ctapobep / blog

My personal blog on IT topics in the form of GitHub issues.
6 stars 0 forks source link

Comparing build tools: Java, Python, JavaScript #14

Open ctapobep opened 2 years ago

ctapobep commented 2 years ago

Java

This platform has one primary tool - Maven. It's well designed and well behaved. It has a local repository of dependencies where it puts all artifacts that it downloads. So if we have a project that needs a libA-1.0.0 and another project that uses libA-2.0.0, then both of them will reside in the local repository. Then each project has a list of dependencies that it references and those are added to the classpath.

Maven has conventions that most teams follow, but it allows to override some of them. Since IDE's have universal support for Maven, they read settings (dependency list, folders, plugins, etc) and allow the project to be easily opened on a new machine.

There are other less popular build tools:

No matter which tool we use, it packs our code along with its dependencies into a target archive (file like JAR, WAR, etc). And that file either contains everything it needs already (including the app server) or it can be deployed into a pre-installed app server on the target machine.

Note, that unlike most of build tools, Maven has a notion of build lifecycle. It knows that if you want to build a project, it first needs to run tests. If you want to run tests, you first need to compile. And it's all standardized - same names of phases/goals across any Java project that you're going to run into. I think this is one of the features that makes Maven the best build tool across all other tools and other platforms (well, I haven't tried .NET though - maybe they have something interesting too).

Python

This story is much more complicated, so buckle up. We need to solve several problems: managing dependencies, isolating projects, project building, installing app's dependencies on the target env. Each problem comes with its own tools and solutions.

While in Java each app needs to list all its dependencies one by one, in Python you just have a folder with the dependencies. And all of them are accessible to every Python app. In order to install a library, there's a build-in utility called pip. So you do pip install ... and it downloads the artifact from PyPi registry which is similar to Maven's Central Repository.

But if we have multiple projects that rely on different list of dependencies (and their versions!), how do we separate environments? To overcome this problem Python introduced a Virtual Environments (virtualenv as a separate tool, or venv as a recent built-in alternative). The goal of these is to create a set of folders for a particular project. And then another project will have its set of folders with a different set of dependencies. This way the projects are completely isolated. Just don't forget to switch between Virtual Envs when working on different projects.

Okay, but we don't want to install all our dependencies manually with pip. For this we use build tools like distutils or poetry and there doesn't seem to be one primary tool which is used by most of the community. As in many other platforms, these tools:

  1. Run scripts to build and test the project
  2. Manage the dependencies (they fetch them from the same PyPi repository)

But if you think that the same dependencies are then used to deploy the app, prepare for a surprise. These dependencies are actually used only to build the project locally. During the build you actually produce a ZIP or Egg for your project only, so how do we install the dependencies? When installing our Python app on the target machine, we again need to use pip. In order not to do this manually, you can list the dependencies in requirements.txt file. And some build tools (like Poetry) have a command to generate Requirements file from their own config.

Note, that when listing dependencies for build tools, similar to other teenage platforms, pythonists specify range versions. So instead of saying some-library:1.1.2 developers usually write some-library:^1.1.2. This means that we're not referencing a particular version - in reality the build tool has a right to download a newer one. Of course this makes the build process unstable - if I build a project on my local machine successfully, it doesn't mean it wouldn't fail on a Build Server because it used a newer dependency. So to solve a problem that we just created there's a separate (locked) list of dependencies that can be generated from the original list and committed. Now we're done (phew!).

JavaScript

This one is closer to Python than to Java, but unlike Python JavaScript's version of pip (npm) can have a project-level config file where you list dependencies. Other similarities:

Unlike pip Python, npm is a project-descriptor. Meaning you put project info there (like name and version). And it's also a build tool - you can list commands to run there. So you'd think there's no need for a separate build tool, right?

Well, npm has very primitive build capabilities and doesn't allow plugins to be written for the build process. So it's not enough for complicated projects. And therefore another set of build tools exists (like Grunt, Gulp, Webpack) for more complicated builds.

To complicate it a little more, since JS packages want to use CSS packages from time to time, for web development there could be additional tools like Yarn for that.