Search code examples
javacommand-linecompiler-errorscompilationjavacompiler

Compiling and running java project with many dependencies classes in other directories


I have this structure of all my directories:

enter image description here

I am trying to compile and execute ServerMain and ClientMain. The following compilation worked:

src>javac -cp .;../lib/gson-2.8.2.jar client/gui/*.java client/net/*.java client/*.java server/*.java server/net/*.java server/exceptions/*.java server/controller/*.java server/data/*.java common/*.java

I have to execute the program not in src, but in the previous directory, because in the code there is a function which read from configuration file in the cfg directory.

I tried this command to execute but I have a lot of errors: ClassesNotFound or Errors in the path of cfg file (which is /cfg/server.cfg in the code) or the ServerMain is not found.

The following command is executed in the previous directory of src which is PROJECT in the images

java -cp lib/gson-2.8.2.jar src/server/WinsomeServerMain

How do I execute properly?


Solution

  • The only really correct answer is: Toss all this stuff in the bin; go get yourself a build system, such as maven or gradle.

    Why?

    javac isn't particularly good at compiling many interconnected source files in different directories, and yet, just lumping all your code into one gigantic package isn't a good idea. These tools are. You just.. tell them to compile. They 'know' that java sources are in src/main/java, and can be easily adjusted to consider 2 separate 'projects' (src/client/java and src/server/java, in your case), and even deliver separate jars for these.

    There's also the issue of dependency management: With these tools you just write a so-called GAV string (Group/Artifact/Version), such as com.google.gson::gson::2.9.0 in a config file and maven/gradle just takes care of it. It knows where to find gson online and downloads it for you, and ensures it's on all the relevant classpaths automatically. This is nice: Sticking all those deps in your source control system means the source control size skyrockets which is annoying, so if not in source control, how do you transfer gson between computers / developers? The answer is: You don't - every system that needs it just downloads it automatically. Which maven and gradle can do for you.

    Finally, you get 'just compile ALL the files' for free, you don't need to list 6 directories, as you are now. That's nice.

    But I really want to use javac

    You'd be wrong. But if you must:

    Error 1 in your snippets: src abuse

    Your javac command compiles all the various files in place, meaning, you now have .class files in a folder structure that includes src and now your project structure is lying to you. This isn't useful. If you really intend for java and class files to live in the same dir, then just get rid of src in the hierarchy. It shuold then just be yourMainProjectDir/client/net/MyClient.java and next to it, MyClient.class. Alternatively, use the -d switch to tell javac to output to a specific directory. For example, bin:

    javac -d bin -cp 'lib/*' client/gui/*.java ..and all other dirs here..
    

    Error 2 - filename instead of a fully qualified class name

    java does not run files. It runs classes. The sequential argument (which in your example was src/server/WinsomeServerMain) is a fully qualified classname. Something like java.lang.String, not something like java/lang/String.class. java will then scan each directory listed in the classpath (which ARE files/directories, and passed with -cp) for that resource.

    In your case, you need 2 separate things on the classpath: gson, and your own class files, which you just compiled.

    Thus:

    java -cp 'lib/*:bin' server.WinsomeServerMain
    

    A few things are happening here:

    • I don't actually know what your packages are, it's not clear from your question. You should have a package xyz; statement at the top of your WinsomeServerMain.java file. If it's server, the above is 'correct'. If there is no package statement, you need one, packageless is invisible to everything except other stuff in its own package and should be abandoned the moment you introduce any hierarchy of any sort.

    • Java can handle * in classpaths, but you must let java do the unpacking. On just about every shell environment except plain jane windows, the shell will see that * and unpack it for you, you don't want that, hence, you MUST use single quotes to tell the shell not to act on it. Also, java is very simplistic and just understands *. It does not understand *.jar, so don't type that. A classpath root contains classpaths. Hence, bin is a root (as the class files are directly in it), but lib is not (because the classfiles aren't in lib - they are in a jar file, and the jar file is in lib). Hence, that isn't a typo: bin is listed on its own, but it's lib/*. This means you can put whatever libraries you want in lib and lib/* will get them all.

    How to deal with config files

    If those config files are intended to be read only ('shipped with your app' - static data that never changes and that you know at compile time), you should stick them in the jar file and read them with getResourceAsStream. If they are intended to be user configurable, they shouldn't be 'with' your class files - class files are code, and code should optimally be placed in a read-only location (at least, as far as the code is concerned). That is why sticking editable files next to them is a bad idea.

    A good solution is to ship templated versions inside the jar (read them with YourClass.class.getResourceAsStream("/configtemplate.txt") - which will find that file if it's in the root of the jar. On start, read Paths.get(System.getProperty("user.home"), "myapp.config") and if it is missing, create it, filling it with the template and telling the user to go edit it.

    That writes to a user's home dir (C:\Users\YourUserName on windows, /home/youracct on linux, /Users/youracct on mac, etc), which is intended for this purpose, and is definitely writable.

    Better deployment

    Yet another advantage of build systems is that they make creation of nice deploys easier: You really want your files in a jar file that specifies its own classpath. build tools can do that. You optimally want to 'ship' your app such that someone ends up with:

    C:\Program Files\YourApp\YourApp.jar
    C:\Program Files\YourApp\lib\gson.jar
    

    and that the only thing that is needed to run the app is to type java -jar YourApp.jar or just doubleclick YourApp.jar in the explorer - and for all that to be platform agnostic (same deal on windows and macs). You can do that - jars can specify their own classpath which is resolved relative to the jar. This involves including a manifest that here would have key Class-Path: lib/gson.jar inside it. You can do it by hand (jar cvmf MyManifest.mf MyApp.jar client/ui/*.class client/*.class etcetera - something like that, where you create the MyManifest.mf text file and list that key inside it), but there are so many steps here, and gradle/maven automate it all.