diff --git a/.gitignore b/.gitignore index 570cd0f..4e93986 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ build src/ncc/ThirdParty/defuse/php-encryption/autoload_spl.php src/ncc/ThirdParty/jelix/version/autoload_spl.php src/ncc/ThirdParty/nikic/PhpParser/autoload_spl.php +src/ncc/ThirdParty/php_parallel_lint/php_console_color/autoload_spl.php src/ncc/ThirdParty/Symfony/polyfill-ctype/autoload_spl.php src/ncc/ThirdParty/Symfony/polyfill-mbstring/autoload_spl.php src/ncc/ThirdParty/Symfony/polyfill-uuid/autoload_spl.php diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..7a5e944 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,33 @@ +image: php:8.1 + +before_script: + # Install some stuff that the image doesn't come with + - apt update -yqq + - apt install git libpq-dev libzip-dev zip make wget gnupg -yqq + + # Install phive + - wget -O phive.phar https://phar.io/releases/phive.phar + - wget -O phive.phar.asc https://phar.io/releases/phive.phar.asc + - gpg --keyserver hkps://keys.openpgp.org --recv-keys 0x9D8A98B29B2D5D79 + - gpg --verify phive.phar.asc phive.phar + - chmod +x phive.phar + - mv phive.phar /usr/local/bin/phive + + # install phpab + - phive install phpab --global --trust-gpg-keys 0x2A8299CE842DD38C + +build: + script: + - make tar + rules: + - if: $CI_COMMIT_BRANCH + +release: + script: + - make tar + - mv build/build.tar.gz build/ncc_$CI_COMMIT_TAG.tar.gz + artifacts: + paths: + - build/ncc_$CI_COMMIT_TAG.tar.gz + rules: + - if: $CI_COMMIT_TAG \ No newline at end of file diff --git a/.idea/copyright/Nosial.xml b/.idea/copyright/Nosial.xml new file mode 100644 index 0000000..ea25c5d --- /dev/null +++ b/.idea/copyright/Nosial.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..85fcaf5 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/ncc.iml b/.idea/ncc.iml index 2c5c861..f554332 100644 --- a/.idea/ncc.iml +++ b/.idea/ncc.iml @@ -8,6 +8,8 @@ + + diff --git a/.idea/php.xml b/.idea/php.xml index a87bd36..dec4919 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -1,11 +1,26 @@ + + + + + + - + + + + + \ No newline at end of file diff --git a/.idea/scopes/Autoloaders.xml b/.idea/scopes/Autoloaders.xml new file mode 100644 index 0000000..e193470 --- /dev/null +++ b/.idea/scopes/Autoloaders.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/Installer_Source_files.xml b/.idea/scopes/Installer_Source_files.xml new file mode 100644 index 0000000..f4cf65d --- /dev/null +++ b/.idea/scopes/Installer_Source_files.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/NCC_Source_files.xml b/.idea/scopes/NCC_Source_files.xml index 6e38ebb..a65b319 100644 --- a/.idea/scopes/NCC_Source_files.xml +++ b/.idea/scopes/NCC_Source_files.xml @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/.idea/scopes/Symfony_Filesystem.xml b/.idea/scopes/Symfony_Filesystem.xml new file mode 100644 index 0000000..00b6846 --- /dev/null +++ b/.idea/scopes/Symfony_Filesystem.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/Symfony_Process.xml b/.idea/scopes/Symfony_Process.xml new file mode 100644 index 0000000..7d46ad8 --- /dev/null +++ b/.idea/scopes/Symfony_Process.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/Symfony_Uid.xml b/.idea/scopes/Symfony_Uid.xml new file mode 100644 index 0000000..0b4c6e4 --- /dev/null +++ b/.idea/scopes/Symfony_Uid.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/Symfony_Yaml.xml b/.idea/scopes/Symfony_Yaml.xml new file mode 100644 index 0000000..d2796d6 --- /dev/null +++ b/.idea/scopes/Symfony_Yaml.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/Symfony_polyfill_ctype.xml b/.idea/scopes/Symfony_polyfill_ctype.xml new file mode 100644 index 0000000..93bc242 --- /dev/null +++ b/.idea/scopes/Symfony_polyfill_ctype.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/Symfony_polyfill_mbstring.xml b/.idea/scopes/Symfony_polyfill_mbstring.xml new file mode 100644 index 0000000..9125e7b --- /dev/null +++ b/.idea/scopes/Symfony_polyfill_mbstring.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/Symfony_polyfill_uuid.xml b/.idea/scopes/Symfony_polyfill_uuid.xml new file mode 100644 index 0000000..fc04e03 --- /dev/null +++ b/.idea/scopes/Symfony_polyfill_uuid.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/defuse.xml b/.idea/scopes/defuse.xml new file mode 100644 index 0000000..3c1064b --- /dev/null +++ b/.idea/scopes/defuse.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/defuse_php_encryption.xml b/.idea/scopes/defuse_php_encryption.xml new file mode 100644 index 0000000..bff7e6a --- /dev/null +++ b/.idea/scopes/defuse_php_encryption.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/jelix.xml b/.idea/scopes/jelix.xml new file mode 100644 index 0000000..6e134aa --- /dev/null +++ b/.idea/scopes/jelix.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/jelix_version.xml b/.idea/scopes/jelix_version.xml new file mode 100644 index 0000000..89f8c5e --- /dev/null +++ b/.idea/scopes/jelix_version.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/nikic.xml b/.idea/scopes/nikic.xml new file mode 100644 index 0000000..471928a --- /dev/null +++ b/.idea/scopes/nikic.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/nikic_PhpParser.xml b/.idea/scopes/nikic_PhpParser.xml new file mode 100644 index 0000000..22f7509 --- /dev/null +++ b/.idea/scopes/nikic_PhpParser.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/theseer.xml b/.idea/scopes/theseer.xml new file mode 100644 index 0000000..c46f520 --- /dev/null +++ b/.idea/scopes/theseer.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/theseer_Autoload.xml b/.idea/scopes/theseer_Autoload.xml new file mode 100644 index 0000000..f64a6e8 --- /dev/null +++ b/.idea/scopes/theseer_Autoload.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/theseer_DirectoryScanner.xml b/.idea/scopes/theseer_DirectoryScanner.xml new file mode 100644 index 0000000..72d0ec3 --- /dev/null +++ b/.idea/scopes/theseer_DirectoryScanner.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f7a58e1 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,36 @@ + +# Code of Conduct + +We are committed to maintaining a welcoming and inclusive environment for all contributors. In order to ensure that +everyone feels safe and respected, we have established the following code of conduct. + +## Our Standards + +We expect all contributors to: + +- Be respectful and considerate of others +- Use inclusive language +- Avoid demeaning, discriminatory, or harassing behavior +- Respect the boundaries of others + +We will not tolerate any behavior that does not align with these standards. + +## Consequences of Unacceptable Behavior + +Unacceptable behavior will not be tolerated and may result in consequences such as warning, blocking of access, or +permanent removal from the project. + +## Reporting Unacceptable Behavior + +If you witness or experience any behavior that is not aligned with our code of conduct, please report it immediately by +contacting the project maintainers. You can open an issue on the project's repository at [https://git.n64.cc/nosial/ncc](https://git.n64.cc/nosial/ncc). + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). + +## License + +This Code of Conduct is licensed under the Creative Commons Attribution 4.0 International License. To view a copy of +this license, visit [http://creativecommons.org/licenses/by/4.0/](http://creativecommons.org/licenses/by/4.0/). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2cd7c78 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing to NCC + +We welcome contributions to NCC! If you have an idea for how to improve the project, please don't hesitate to reach out. +There are many ways to contribute, and we appreciate all forms of support. + +## How to Contribute + +- **Report a bug**: If you think you have found a bug in NCC, please open an issue and include as much detail as + possible to help us reproduce and fix the issue. +- **Request a feature**: Have an idea for a new feature or improvement? Open an issue to start a discussion. +- **Submit a pull request**: If you have developed a fix for a bug or have implemented a new feature, please submit a + pull request for review. + +## Code of Conduct + +We are committed to maintaining a welcoming and inclusive environment for all contributors. Please read and follow our +[Code of Conduct](https://git.n64.cc/nosial/ncc/CODE_OF_CONDUCT.md). + +## License + +By contributing to NCC, you agree that your contributions will be licensed under the MIT License. + +## Attribution + +NCC is Copyright (c) Nosial. Please include the copyright notice in any distributed code. diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 629dfaa..31793f0 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -5,30 +5,839 @@ NCC, from basic installation, basic usage, standards and much more. ## Table of contents - - Introduction - - What is NCC? - - Advantages over other software - - ------------------------------------------------------------------------------------ + +* [NCC Documentation](#ncc-documentation) + * [Table of contents](#table-of-contents) + * [Introduction](#introduction) + * [What is NCC?](#what-is-ncc) +* [Building NCC from source](#building-ncc-from-source) + * [Requirements to build](#requirements-to-build) + * [Installing phpab](#installing-phpab) + * [Building NCC](#building-ncc) + * [Redist](#redist) + * [Tar](#tar) +* [Installing NCC](#installing-ncc) + * [Command line arguments](#command-line-arguments) +* [Uninstalling NCC](#uninstalling-ncc) +* [NCC Command-line Interface](#ncc-command-line-interface) + * [Management Commands](#management-commands) + * [Utility Commands](#utility-commands) + * [Options](#options) +* [Projects](#projects) + * [Creating a project](#creating-a-project) + * [project.json structure](#projectjson-structure) + * [project](#project) + * [project.compiler](#projectcompiler) + * [project.update_source](#projectupdatesource) + * [project.update_source.repository](#projectupdatesourcerepository) + * [assembly](#assembly) + * [execution_policies](#executionpolicies) + * [execution_policy](#executionpolicy) + * [execution_policy.execute](#executionpolicyexecute) + * [execution_policy.exit_handlers](#executionpolicyexithandlers) + * [exit_handler](#exithandler) + * [installer](#installer) + * [build](#build) + * [dependency](#dependency) + * [Source Types](#source-types) +* [Execution Policies](#execution-policies) + * [Supported Runners](#supported-runners) + * [Configuring Runners](#configuring-runners) +* [Remote Sources](#remote-sources) + * [Supported sources](#supported-sources) + * [Default sources](#default-sources) + * [Managing sources](#managing-sources) + * [Adding a source](#adding-a-source) + * [Removing a source](#removing-a-source) + * [Listing sources](#listing-sources) + * [Credential Management](#credential-management) + * [Adding credentials](#adding-credentials) + * [Removing credentials](#removing-credentials) + * [Listing credentials](#listing-credentials) +* [UUIDs](#uuids) +* [Versioning](#versioning) + * [Version Format](#version-format) + * [Version Format Compatibility](#version-format-compatibility) +* [Naming a package](#naming-a-package) + * [Naming conventions](#naming-conventions) + * [References](#references) +* [Error Codes](#error-codes) + +## Introduction -# Introduction (May 24, 2022) - -This section serves the basic introduction of NCC, what it's used for and how you can -use it in your own projects or use it to run and build other projects that are designed -to be used with NCC. +This section serves the basic introduction of NCC, what it's used for and how you can use it in your own projects or use +it to run and build other projects that are designed to be used with NCC. ## What is NCC? -NCC (*Acronym for **N**osial **C**ode **C**ompiler*) is a multi-purpose compiler, -package manager and toolkit. Allowing projects to be managed and built more easily -without having to mess with all the traditional tools that comes with your language -of choice. Right now NCC only supports PHP as it's written in PHP but extensions -for other languages/frameworks can be built into the software in the future when -the need comes for it. +NCC (*Acronym for **N**osial **C**ode **C**ompiler*) is a multi-purpose compiler, package manager and toolkit. Allowing +projects to be managed and built more easily without having to mess with all the traditional tools that comes with your +language of choice. Right now NCC only supports PHP as it's written in PHP but extensions for other languages/frameworks +can be built into the software in the future when the need comes for it. -NCC can make the process of building your code into a redistributable package much -more efficient by treating each building block of your project as a component that -is interconnected in your environment instead of the more popular route taken by -package/dependency managers such as [composer](https://getcomposer.org/), -[npm](https://www.npmjs.com/) or [pypi (or pip)](https://pypi.org/). \ No newline at end of file +NCC can make the process of building your code into a redistributable package much more efficient by treating each +building block of your project as a component that is interconnected in your environment instead of the more popular +route taken by package/dependency managers such as [composer](https://getcomposer.org/),[npm](https://www.npmjs.com/) or +[pypi (or pip)](https://pypi.org/). + + +------------------------------------------------------------------------------------ + +# Building NCC from source + +Building NCC from source is easy with very few requirements to start building. At the moment ncc can only be debugged or +tested by building a redistributable source and installing it. + +## Requirements to build + +- php8.0+ +- php-mbstring +- php-ctype +- php-common (covers tokenizer & posix among others) +- make +- phpab +- tar *(optional)* + +## Installing phpab + +phpab is also known as [PHP Autoload Builder](https://github.com/theseer/Autoload), phpab is an open source tool used +for creating autoload files, ncc needs this tool in order to generate it's autoload files whenever there's any changes +to its source code. + +This tool is only required for building and or creating a redistributable package of ncc. This component is not +required to be installed to use ncc. + +for some components that require static loading, ncc will automatically load it using its own +[autoloader](src/autoload/autoload.php) + +The recommended way to install phpab is by using [phive](https://phar.io/), if you don't have phive installed you can +install it by running these commands in your terminal (from the official documentation) + +```shell +wget -O phive.phar https://phar.io/releases/phive.phar +wget -O phive.phar.asc https://phar.io/releases/phive.phar.asc +gpg --keyserver hkps://keys.openpgp.org --recv-keys 0x9D8A98B29B2D5D79 +gpg --verify phive.phar.asc phive.phar +chmod +x phive.phar +sudo mv phive.phar /usr/local/bin/phive +``` + +Once phive is installed, you can run the final command to install phpab + +```shell +sudo phive install phpab --global +``` + +or you can run this command to install it locally + +```shell +phive install phpab +``` + +**Note:** Optionally, you may want to have `phab` available in your `$PATH`, this can be done with this command. +*(Replace `x.xx.x` with your version number)* this is if you installed it locally + +```shell +ln -s /home/user/.phive/phars/phpab-x.xx.x.phar /usr/local/bin/phpab +``` + +## Building NCC + +First, navigate to the main directory of NCC's source code where the [Makefile](Makefile) is present. If you +already attempted to or had built ncc before, it's recommended to use `make clean` before building. + +### Redist + +Running `redist` from the Makefile will generate all the required autoloader for ncc and move all the required files +into one redistributable source folder under a directory called `build/src` + +```shell +make redist +``` + + +### Tar + +Running `tar` will run redist before packaging the redistributable source into a tar.gz file that can be distributed to +other machines, this process is not a requirement. + +```shell +make tar +``` + +Once you have a populated `build/src` folder, you can simply run execute the `installer` file to install your build of +ncc onto the running machine. + +------------------------------------------------------------------------------------ + +# Installing NCC + +Installing NCC is easy, you can either download the redistributable source from the [releases](https://git.n64.cc/nosial/ncc/-/releases) +page or you can build it from source using the instructions above. + +Once you have the redistributable source, you can simply run execute the `INSTALL` file to install ncc onto the running +machine. + +## Command line arguments + +The installer accepts a few command line arguments that can be used to customize the installation process. + +`--help` Displays the help message + +`--auto` Automatically installs ncc without asking for user input. + +**Note:** To install composer along with ncc, you must also provide the `--install-composer` argument. + +`--install-composer` Installs composer along with ncc. By default, ncc will not install composer and during the +installation process you will be asked if you want to install composer along-side ncc, this will not conflict +with any existing composer installation. + +`--install-dir` Specifies the directory where ncc will be installed to. By default, ncc will be installed to `/etc/ncc` + +`--bypass-cli-check` Bypasses the check in the installer that checks if the installer is being run from the command +line, this is useful if you want to install ncc from a script. + +`--bypass-checksum` Bypasses the checksum check in the installer, this is useful if you made modifications to the +installation files and want to install a modified version of ncc. + +But this isn't recommended and the proper way to do this is to modify the source code and build ncc from source, +the Makefile task will automatically rebuild the checksum file for you. + + +------------------------------------------------------------------------------------ + +# Uninstalling NCC + +Uninstalling NCC is easy, simply delete the directory where ncc was installed to, by default this is `/etc/ncc`. + +It's recommended to run `ncc package --uninstall-all` before uninstalling ncc, this will uninstall all the packages +that were installed using ncc and remove any artifacts that were created by these packages. + +**Note:** + - To delete all the data that ncc has created, you can also delete the `/var/ncc` directory. + - Finally, remove the symlink that was created in `/usr/local/bin`to the `ncc` entry point file. + +------------------------------------------------------------------------------------ + +# NCC Command-line Interface + +NCC provides a command-line interface that can be used to manage packages, create projects, compile source code, manage +remote sources, configure ncc, and more. You can run `ncc --help` to see a list of all the available commands. + +![ncc cli](assets/ncc_cli.png) + +## Management Commands + +Management commands are used to manage ncc's configuration, remote sources, and packages. + + +`project` Manage or create a project (*see [Projects](#projects) section*) + +`package` Manage packages + +`source` Manage remote sources + +`config` Manage ncc's configuration + +## Utility Commands + +Utility commands are used to perform tasks in the current directory or project. + +`build` Compile source code of the project + +`exec` Executes a package's entry point file (package must be installed) + +## Options + +NCC also accepts a few command line arguments that can be used to alter the behavior of the command-line interface. + +`-l , --log-level ` Sets the log level, this can be one of `debug`, `verbose`, `info`, `warn`, `error`, `fatal` + +`-v, --version` Displays the version of ncc + +`-h, --help` Displays the help message + +`--basic-ascii` Renders some messages using basic ASCII characters instead of unicode characters + +`--no-color` Disables colored output + +`--no-banner` Omits displaying the NCC graphical banner + +------------------------------------------------------------------------------------ + +# Projects + +A project is a directory that contains all the source files to your program, it's similar to a workspace in other IDEs. +Usually contains a `project.json` file which contains all the information about the project that ncc needs to know. + +This can include the name of the program, the version of the program, the author of the program, the dependencies of the +program, build configurations, and more. + +This section will cover the basics of creating a project and managing it and the technical details of the `project.json` +file. + + +## Creating a project + +This is the first step in using ncc, you must create a project before you can do anything else (*not really because you +can install packages without needing to create a project and run them directly, but you get the point*) + +The NCC command-line tool provides a management command called `project` that can be used to create a new project +or to manage an existing project. + +```shell +ncc project create --package "com.example.program" --name "Example Program" +``` + +This command will create a new project in the current directory, the `--package` argument specifies the package name of +the project, this is used to identify the project and to avoid conflicts with other projects that may have the same name. + +The `--name` argument specifies the name of the project, this is used to display the name of the project in the project +manager and in the project settings. This doesn't have to be the same as the package name or unique. + +**Note:** If the options are not provided, the command will prompt you for the package name and the project name. + +For more information about the project command, you can run `ncc project --help` to display the help message. + +## project.json structure + +The `project.json` file is a JSON file that contains all the information about the project. + +When a project is created, the `project.json` file is automatically created and populated with the default values, you +can modify this file to change the default values or to add more information about the project. + +This section will go over the structure of the `project.json` file and what each field does. + +### project + +The `project` field contains information about the project, such as what compiler extension to use, options to pass on +to the compiler, and more. + +| Name | Type | Required | Description | +|---------------|--------------------------------------|----------|----------------------------------------------------------------------------------------------------| +| compiler | [project.compiler](#projectcompiler) | Yes | The compiler extension that the project uses to compile the program | +| options | `array` | No | An array of options to pass on to the compiler, the options vary depending on the compiler and NCC | +| update_source | `project.update_source` | No | The source for where the program can fetch updates from | + +### project.compiler + +The `project.compiler` field contains information about the compiler extension that the project uses to compile +the program. + +| Name | Type | Required | Description | +|-----------------|----------|----------|------------------------------------------------------------------------------------------------| +| extension | `string` | Yes | The name of the compiler extension that the project uses to compile the program | +| minimum_version | `string` | No | The minimum version of the compiler extension that the project requires to compile the program | +| maximum_version | `string` | No | The maximum version of the compiler extension that the project requires to compile the program | + +### project.update_source + +The `project.update_source` field contains information about the source where the program can fetch updates from. + +| Name | Type | Required | Description | +|------------|------------------------------------|----------|-----------------------------------------------------------------------------------------------------------------------| +| source | `string` | Yes | The source where the program can fetch updates from, see [Remote Sources](#remote-sources) for additional information | +| repository | `project.update_source.repository` | Yes | The source to configure in NCC when installing the package | + +### project.update_source.repository + +The `project.update_source.repository` field contains information about the source to configure in NCC when installing +the package. This allows you to set up a remote source that your package can use to fetch updates from, this is useful +if you want to distribute your program to other people. + +It would be useful to read more about [Remote Sources](#remote-sources) before continuing. + +| Name | Type | Required | Description | +|------|----------|----------|---------------------------------------------------------------------------------------| +| name | `string` | Yes | The name of the source to configure in NCC when installing the package (eg; `github`) | +| type | `string` | Yes | The API type to use with this source, see [Supported sources](#supported-sources) | +| host | `string` | Yes | The host of the source, this is the domain name of the source (eg; `api.github.com`) | +| ssl | `bool` | No | Whether to use SSL or not when connecting to this source | + + +### assembly + +The `assembly` field contains metadata about the program, such as the name, version, description, so on. + +| Name | Type | Required | Description | +|-------------|----------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| name | `string` | Yes | The name of the package, this is used to display the name of the package (eg; `Example Program`) | +| package | `string` | Yes | The package name of the program, this is used to identify the package and to avoid conflicts with other packages that may have the same name, see [Naming a package](#naming-a-package) for additional information | +| description | `string` | No | The description of the package, this is used to display a description of the package when installing | +| company | `string` | No | The company that created the package, this is used to display the company that created the package when installing | +| product | `string` | No | The product that the package is a part of, this is used to display the product that the package is a part of when installing | +| copyright | `string` | No | The copyright of the package | +| trademark | `string` | No | The trademark of the package | +| version | `string` | Yes | The version of the package, see [Versioning](#versioning) for additional information | +| uuid | `string` | Yes | The UUID of the package, see [UUIDs](#uuids) for additional information | + +### execution_policies + +The `execution_policies` field contains information about the execution policies that the program uses, such as +the execution policy that the program uses to run additional programs during different stages of the installation +process of the package or used as the main execution policy for the program. + +Note that this field is an array of `execution_policy` objects, see [execution_policy](#executionpolicy) for additional +information. + +For more information about execution policies, see [Execution Policies](#execution-policies). + +#### execution_policy + +The `execution_policy` object contains information about the execution policy. + +| Name | Type | Required | Description | +|---------------|----------------------------------|----------|----------------------------------------------------------------------------------------------------| +| name | `string` | Yes | The name of the execution policy, this is used to identify the execution policy | +| runner | `string` | Yes | The name of the runner that the execution policy uses, see [Supported runners](#supported-runners) | +| message | `string` | No | The message to display when the execution policy is being run | +| execute | `execution_policy.execute` | Yes | The execution policy to run when the execution policy is being run | +| exit_handlers | `execution_policy.exit_handlers` | No | The exit handlers to run when the execution policy has finished running | + +#### execution_policy.execute + +The `execution_policy.execute` object contains information about how to run the execution policy when triggered. + +| Name | Type | Required | Description | +|-----------------------|------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| target | `string` | Yes | The target file to run when the execution policy is triggered, file path is relative to the location of your project.json file (eg; scripts/main.php) | +| working_directory | `string` | No | The working directory to run the process in. If not specified, the working directory will be your current working directory. | +| options | `string[]` | No | The options to pass to the process when running it. (eg; ["--foo", "bar", "-f"]) | +| environment_variables | `string[]` | No | The environment variables to pass to the process when running it. (eg; ["FOO=bar"]) | +| silent | `bool` | No | Whether to run the process silently or not. If not specified, the process will not be run silently. | +| tty | `bool` | No | Whether to run the process in a TTY or not. If not specified, the process will not be run in a TTY. | +| timeout | `int` | No | The timeout of the process in seconds. If not specified, the process will not have a timeout. | +| idle_timeout | `int` | No | The idle timeout of the process in seconds. If not specified, the process will not have an idle timeout. | + +#### execution_policy.exit_handlers + +The `execution_policy.exit_handlers` object contains information about how to run the exit handlers when the execution +policy has finished running. This is useful for running additional policies when the process has exited in a specific +way. + +The two handlers that can be executed automatically despite the exit code are `success` and `error`. Which means if the +process exits with a success exit code, the `success` handler will be run, and if the process exits with an error exit +code, the `error` handler will be run. The `warning` handler will only be run if the process exits with specified exit +code. + +| Name | Type | Required | Description | +|---------|----------------|----------|-------------------------------------------------------------------------------| +| success | `exit_handler` | No | The exit handler to run when the process has exited successfully. | +| warning | `exit_handler` | No | The exit handler to run when the process has exited with a warning exit code. | +| error | `exit_handler` | No | The exit handler to run when the process has exited with an error exit code. | + +#### exit_handler + +The `exit_handler` object contains information about how to run the exit handler when the execution policy has finished +running. + +| Name | Type | Required | Description | +|---------------|----------|----------|---------------------------------------------------------------------------------------------------------------------------------------------| +| message | `string` | No | The message to display when the exit handler is triggered | +| end_execution | `bool` | No | Whether to end the execution of the program or not if this exit handler is triggered. If not specified, the program will not end execution. | +| run | `string` | No | The name of the execution policy to run when this exit handler is triggered. | +| exit_code | `int` | No | The exit code that the process must have exited with for this exit handler to be triggered. | + +### installer + +The `installer` field contains allows you to configure the execution of policies during different stages of the +installation process of the package. Note that these files only accepts an array of strings, which are the names of +the execution policies that you want to run during the specified stage. NCC will reject the package if the execution +policy does not exist. + +| Name | Type | Required | Description | +|----------------|------------|----------|-------------------------------------------------------------------------| +| pre_install | `string[]` | No | The execution policies to run before the installation of the package. | +| post_install | `string[]` | No | The execution policies to run after the installation of the package. | +| pre_uninstall | `string[]` | No | The execution policies to run before the uninstallation of the package. | +| post_uninstall | `string[]` | No | The execution policies to run after the uninstallation of the package. | +| pre_update | `string[]` | No | The execution policies to run before the update of the package. | +| post_update | `string[]` | No | The execution policies to run after the update of the package. | + + +### build + +The `build` field contains the configuration for the build process of the package. This field is required and must be +configured correctly for the package to be built successfully. + +| Name | Type | Required | Description | +|-----------------------|-------------------------|----------|----------------------------------------------------------------------------------------------------------------------------| +| source_path | `string` | Yes | The path to the source directory of the package. (eg; src) | +| default_configuration | `string` | Yes | The default build configuration to use when building the package. | +| exclude_files | `string[]` | No | The files to exclude from the build process. | +| options | `string[]` | No | The options to pass to the build process. | +| main | `string` | No | The main execution policy to run when the package is executed, this is like the main entry point of the package. | +| define_constants | `string[]` | No | Environment constants to define during the execution of your program, these values can be accessed by the NCC Runtime API. | +| pre_build | `string[]` | No | The execution policies to run before the build process. | +| post_build | `string[]` | No | The execution policies to run after the build process. | +| dependencies | `dependency[]` | No | The dependencies that the package requires | +| configurations | `build_configuration[]` | No | Predefined build configurations that can be used to produce different builds of the package | + +### dependency + +The `dependency` object contains information about a dependency that the package requires. + +| Name | Type | Required | Description | +|-------------|----------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| name | `string` | Yes | The package name of the dependency (eg; com.example.package) | +| source_type | `string` | No | Where NCC should get the dependency from, accepted values are `static`, `local` or `remote`. If not specified, NCC will assume `remote`. | +| source | `string` | No | The source of the dependency, this can a remote source (see [Remote Sources](#remote-sources)) if the source is `remote` or a a local file path if the source is `static` | +| version | `string` | No | The version of the dependency to use, if not specified, NCC will use the latest version of the dependency. (eg; 'latest') | + +#### Source Types + +Dependency source types are used to specify where NCC should get the dependency from, these are: + +- `static` - This source type is used to specify that the dependency is a local file path, this is useful for dependencies +that are not available on the remote package repository or to bundle dependencies with the package. You can only link to +pre-compiled .ncc packages, otherwise NCC will fail to install the package. +- `local` - This source type is used to specify that the dependency is a local package that is already installed on the +system. This is useful for dependencies that are already installed on the system, and you want to use them in your package +but doesn't necessarily need to pull them from a remote repository or local path. NCC expects the package to be installed +otherwise installing the package will fail unless `--skip-dependencies` is specified. +- `remote` - This source type is used to specify that the dependency is a remote package that is available on the remote +repository. This is the recommended source type to use for dependencies that are available on the remote repository. + +------------------------------------------------------------------------------------ + +# Execution Policies + +Execution policies are the policies that are used to run additional programs during different stages of the installation +or execution of the package. These policies are defined in your project.json `execution_policies` field with unique +names for each policy, so that these policies can be referenced by other policies or by NCC if configured to do so. + +## Supported Runners + +At the moment, NCC only supports a select few "runners" that can be used to run the policies, these runners are: + +- `php` - This runner is used to run PHP scripts, it is the default runner for NCC +- `bash` - This runner is used to run bash scripts +- `python` - This runner is used to run python scripts +- `python2` - This runner is used to run python2 scripts +- `python3` - This runner is used to run python3 scripts +- `perl` - This runner is used to run perl scripts +- `lua` - This runner is used to run lua scripts + + > Note: these runners are not installed by default, you will need to install them yourself. + +## Configuring Runners + +If for some reason NCC cannot automatically detect the runner that you want to use, you can configure the runner yourself +by modifying your configuration file. The configuration file is located at `/var/ncc/ncc.yaml` under the `runners` field. + +Or you can modify the configuration file by running the following command: + +```bash +ncc config -p runners.bash -v /usr/bin/bash +``` + +This will set the `bash` runner to use the `/usr/bin/bash` binary. + + > **Note:** You must have root permissions to modify the configuration file. + +------------------------------------------------------------------------------------ + +# Remote Sources + +Remote Sources are the locations where packages can be downloaded from, they are similar to repositories in other package +managers. They follow a simple syntax that allows you to specify the type of source, the location of the source, and more. + +Examples of sources are: + +- `symfony/process=latest@composer` - This is a package from the `symfony/process` package from the `composer` source +- `nosial/libs.config=latest@n64` - This is a package from the `nosial/libs.config` package from the `git.n64.cc` source + +A full example syntax may look like this: + +``` +/:=@ +``` + +This syntax is used to specify a package from a source, the syntax is split into 4 parts: + +- The vendor of the package +- The name of the package +- The branch of the package (optional) +- The version of the package (optional) +- The name of the source (needs to be configured in ncc) + +## Supported sources + +NCC supports the following sources: + +- `github` - This source uses the GitHub API to fetch packages from GitHub (Included in the default sources) +- `gitlab` - This source uses the GitLab API to fetch packages from GitLab (Can be used with self-hosted GitLab instances) + +Additional support for other sources will be added in the future. + +## Default sources + +NCC comes with a few default sources that are configured by default, these sources are: + +- packagist.org (`composer`) **Note:** This is an internal source that uses `composer` to fetch packages from packagist.org. + this is not configurable by the user. +- api.github.com (`github`) +- gitlab.com (`gitlab`) +- git.n64.cc (`n64`) +- gitgud.io (`gitgud`) + +Additional sources can be added by the user. See [Adding a source](#adding-a-source) for more information. + +## Managing sources + +You can manage sources using the `source` command in the ncc command-line tool. This command can be used to add, remove, +and list sources. For more information about the `source` command, you can run `ncc source --help` to display the help +message. + +### Adding a source + +To add a source, you can use the `add` command in the ncc `source` command-line tool. + +```shell +ncc source add --name "github" --type "github" --host "github.com" --ssl +``` + +This command will add a new source called `github` with the type `github` and the host `github.com`, the `--ssl` option +will tell ncc to use HTTPS instead of HTTP when fetching packages from this source. + +The reason to specify the type of source is to tell ncc what API to use when fetching packages from this source, for +example if you specify the type as `github` then ncc will use the GitHub API to fetch packages from this source so it's +important to specify the correct type when adding a source. + +> **Note:** You need root permissions to add a source + + +### Removing a source + +To remove a source, you can use the `remove` command in the ncc `source` command-line tool. + +```shell +ncc source remove --name "github" +``` + +> **Note:** You need root permissions to remove a source + +> **Note:** Removing a source also removes the ability for some packages to be fetched or updated from this source + + +### Listing sources + +To list all the sources, you can use the `list` command in the ncc `source` command-line tool. + +```shell +ncc source list +``` + +## Credential Management + +Some sources require credentials to be able to fetch packages from them, for example the `gitlab` source requires +credentials to be able to fetch packages from a self-hosted GitLab instance. NCC supports storing credentials for +sources in a secure way using the `cred` command in the ncc command-line tool. + +### Adding credentials + +To add credentials for a source, you can use the `add` command in the ncc `cred` command-line tool. + +```shell +ncc cred add --alias "My Alias" --auth-type login --username "myusername" --password "mypassword" +``` + +To add a private access token as a credential, you can specify the `--auth-type` as `pat` and specify the token as +`--token` instead of providing `--username` and `--password`. + +```shell +ncc cred add --alias "My Alias" --auth-type pat --token="mytoken" +``` + +By default, ncc will encrypt the entry except for the alias using the password/token that you provide. + +However, because it's encrypted you will need to provide the password/token when using the credential since ncc will +not be able to decrypt the entry without a password. To avoid being asked for the password/token every time you use the +credential, you can pass on the `--no-encryption` option to the `cred` command-line tool. + +```shell +ncc cred add --alias "My Alias" --auth-type login --username "myusername" --password "mypassword" --no-encryption +``` + +Encryption is applied individually to each credential, so you can have some credentials encrypted and some not encrypted. + +> **Note:** You need root permissions to add credentials + + +### Removing credentials + +To remove credentials, you can use the `remove` command in the ncc `cred` command-line tool. + +```shell +ncc cred remove --alias "My Alias" +``` + +> **Note:** You need root permissions to remove credentials + + +### Listing credentials + +To list all the credentials, you can use the `list` command in the ncc `cred` command-line tool. this will return +a list of all the credentials that are stored in the credential store with additional information about each entry. + +```shell +ncc cred list +``` + +------------------------------------------------------------------------------------ + +# UUIDs + +UUIDs are used to uniquely identify a package, at the moment ncc doesn't do anything meaningful with UUIDs but in the +future it will be used to identify packages and to prevent conflicts between packages with the same name. + +The standard UUID format used is version 1, which is a time-based UUID. This means that the UUID is generated using +the current time and the MAC address of the computer that generated the UUID. + +`````` +xxxxxxxx-xxxx-1xxx-yxxx-xxxxxxxxxxxx +`````` + +UUIDs are automatically generated when a package is created, you can also manually specify a UUID by editing the +`project.json` file in the project directory, this field is found under `assembly.uuid`, see [assembly](#assembly) for +more information. + +> **Note:** Invalid UUIDs will cause the package to be rejected by ncc + +------------------------------------------------------------------------------------ + +# Versioning + +NCC uses a standard versioning system, this system is based on the [Semantic Versioning](https://semver.org/) system. + +## Version Format + +The version format is as follows: + +`````` +MAJOR.MINOR.PATCH +`````` + +- `MAJOR` is the major version of the package, this version is incremented when a major change is made to the package +- `MINOR` is the minor version of the package, this version is incremented when a minor change is made to the package +- `PATCH` is the patch version of the package, this version is incremented when a patch is made to the package + + +## Version Format Compatibility + +NCC will attempt to convert non-compatible versions to a compatible version when it comes to installing packages that +isn't built for ncc. + +> **Note:** NCC will reject packages with invalid version numbers, sometimes this can happen when the compatibility layer +fails or when the version number is invalid. + +------------------------------------------------------------------------------------ + +# Naming a package + +NCC Follows the same naming convention as Java's naming convention. The purpose of naming a package this way is +to easily create a "Name" of the package, this string of information contains + +- The developer/organization behind the package +- The package name itself + + +## Naming conventions + +Package names are written in all lower-case due to the fact that some operating systems treats file names +differently, for example on Linux `Aa.txt` and `aa.txt`are two entirely different file names because of the +capitalization and on Windows it's treated as the same file name. + +Organizations or small developers use their domain name in reverse to begin their package names, for example +`net.nosial.example` is a package named `example` created by a programmer at `nosial.net` + +Just like the Java naming convention, to avoid conflicts of the same package name developers can use something +different, for example as pointed out in Java's package naming convention developers can instead use something +like a region to name packages, for example `net.nosial.region.example` + + +## References + +For Java's package naming conventions see [Naming a Package](https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html) +from the Oracle's Java documentation resource, as the same rules apply to NCC except for *some* illegal naming +conventions such as packages not being able to begin with `int` or numbers + +------------------------------------------------------------------------------------ + +# Error Codes + +NCC uses error codes to identify errors, these error codes are used to identify errors in the ncc command-line tool +and in the ncc API. + +| Error Code | Name | +|:----------:|:--------------------------------------| +| `-1700` | InvalidProjectConfigurationException | +| `-1701` | FileNotFoundException | +| `-1702` | DirectoryNotFoundException | +| `-1703` | InvalidScopeException | +| `-1704` | AccessDeniedException | +| `-1705` | MalformedJsonException | +| `-1706` | RuntimeException | +| `-1707` | InvalidCredentialsEntryException | +| `-1708` | ComponentVersionNotFoundException | +| `-1709` | ConstantReadonlyException | +| `-1710` | InvalidPackageNameException | +| `-1711` | InvalidVersionNumberException | +| `-1712` | InvalidProjectNameException | +| `-1713` | ProjectAlreadyExistsException | +| `-1714` | AutoloadGeneratorException | +| `-1715` | NoUnitsFoundException | +| `-1716` | UnsupportedPackageException | +| `-1717` | NotImplementedException | +| `-1718` | InvalidPackageException | +| `-1719` | InvalidConstantNameException | +| `-1720` | PackagePreparationFailedException | +| `-1721` | BuildConfigurationNotFoundException | +| `-1722` | InvalidProjectBuildConfiguration | +| `-1723` | UnsupportedCompilerExtensionException | +| `-1724` | InvalidPropertyValueException | +| `-1725` | InvalidVersionConfigurationException | +| `-1726` | UnsupportedExtensionVersionException | +| `-1727` | BuildException | +| `-1728` | PackageParsingException | +| `-1729` | PackageLockException | +| `-1730` | InstallationException | +| `-1731` | UnsupportedComponentTypeException | +| `-1732` | ComponentDecodeException | +| `-1733` | ComponentChecksumException | +| `-1734` | ResourceChecksumException | +| `-1735` | IOException | +| `-1736` | UnsupportedRunnerException | +| `-1737` | VersionNotFoundException | +| `-1738` | UndefinedExecutionPolicyException | +| `-1739` | InvalidExecutionPolicyName | +| `-1740` | ProjectConfigurationNotFoundException | +| `-1741` | RunnerExecutionException | +| `-1742` | NoAvailableUnitsException | +| `-1743` | ExecutionUnitNotFoundException | +| `-1744` | PackageAlreadyInstalledException | +| `-1745` | PackageNotFoundException | +| `-1746` | ComposerDisabledException | +| `-1747` | InternalComposerNotAvailable | +| `-1748` | ComposerNotAvailableException | +| `-1749` | ComposerException | +| `-1750` | UserAbortedOperationException | +| `-1751` | MissingDependencyException | +| `-1752` | HttpException | +| `-1753` | UnsupportedRemoteSourceTypeException | +| `-1754` | GitCloneException | +| `-1755` | GitCheckoutException | +| `-1756` | GitlabServiceException | +| `-1757` | ImportException | +| `-1758` | GitTagsException | +| `-1759` | GithubServiceException | +| `-1760` | AuthenticationException | +| `-1761` | NotSupportedException | +| `-1762` | UnsupportedProjectTypeException | +| `-1763` | UnsupportedArchiveException | +| `-1764` | ArchiveException | +| `-1765` | PackageFetchException | +| `-1766` | InvalidBuildConfigurationException | +| `-1767` | InvalidDependencyConfiguration | +| `-1768` | SymlinkException | diff --git a/LICENSE b/LICENSE index 3fad6f1..723b74e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,355 +1,14 @@ ------------------------- -Nosial - NCC +Copyright 2022-2023 Nosial - All Rights Reserved. -Copyright (C) 2022-2022. Nosial - All Rights Reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the following conditions: -Unauthorized copying of this file, via any medium is strictly prohibited -Proprietary and confidential, written by Zi Xing Narrakas +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. ------------------------- -Symfony - Process - -Copyright (c) 2004-2022 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ------------------------- -defuse - php-encryption - -The MIT License (MIT) - -Copyright (c) 2016 Taylor Hornby -Copyright (c) 2016 Paragon Initiative Enterprises . - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ------------------------- -Symfony - uid - -Copyright (c) 2020-2022 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - ------------------------- -Symfony - polyfill-mbstring - -Copyright (c) 2015-2019 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - ------------------------- -Symfony - polyfill-ctype - -Copyright (c) 2018-2019 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - ------------------------- -Symfony - polyfill-uuid - -Copyright (c) 2018-2019 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - - ------------------------- -dealnews.com, Inc. - Inline ProgressBar CLI - -Copyright (c) 2010, dealnews.com, Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of dealnews.com, Inc. nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - ------------------------- -Symfony - Filesystem - -Copyright (c) 2004-2022 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - ------------------------- -Symfony - Yaml - -Copyright (c) 2004-2022 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ------------------------- -theseer - Autoload - -Autoload Builder - -Copyright (c) 2010-2016 Arne Blankerts and Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of Arne Blankerts nor the names of contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - ------------------------- -theseer - DirectoryScanner - -DirectoryScanner - -Copyright (c) 2009-2014 Arne Blankerts -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of Arne Blankerts nor the names of contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - ------------------------- -nikic - PhpParser - -BSD 3-Clause License - -Copyright (c) 2011, Nikita Popov -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ------------------------- -jelix - version - -Copyright (C) 2009-2016 Laurent Jouanneau - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile index 6931230..f2a4382 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -PHPCC=/usr/bin/php -PHPAB=/usr/bin/phpab +PHPCC:=$(shell which php) +PHPAB:=$(shell which phpab) BUILD_PATH=build SRC_PATH=src @@ -80,25 +80,28 @@ $(SRC_PATH)/ncc/autoload_spl.php: $(SRC_PATH)/ncc/Objects \ $(SRC_PATH)/ncc/Runtime \ $(SRC_PATH)/ncc/Utilities \ - $(SRC_PATH)/ncc/ncc.php + $(SRC_PATH)/ncc/ncc.php \ + $(SRC_PATH)/ncc/Runtime.php redist: autoload rm -rf $(BUILD_PATH)/src mkdir -p $(BUILD_PATH)/src cp -rf $(SRC_PATH)/ncc/* $(BUILD_PATH)/src - cp $(SRC_PATH)/installer/installer $(BUILD_PATH)/$(SRC_PATH)/INSTALL - cp $(SRC_PATH)/installer/ncc.sh $(BUILD_PATH)/$(SRC_PATH)/ncc.sh - cp $(SRC_PATH)/config/ncc.yaml $(BUILD_PATH)/$(SRC_PATH)/default_config.yaml; - cp $(SRC_PATH)/config/ncc.yaml $(BUILD_PATH)/$(SRC_PATH)/CLI/template_config.yaml; - cp $(SRC_PATH)/installer/extension $(BUILD_PATH)/$(SRC_PATH)/extension - chmod +x $(BUILD_PATH)/$(SRC_PATH)/INSTALL - cp LICENSE $(BUILD_PATH)/$(SRC_PATH)/LICENSE - cp README.md $(BUILD_PATH)/$(SRC_PATH)/README.md - cp $(SRC_PATH)/installer/hash_check.php $(BUILD_PATH)/$(SRC_PATH)/hash_check.php; $(PHPCC) $(BUILD_PATH)/$(SRC_PATH)/hash_check.php; rm $(BUILD_PATH)/$(SRC_PATH)/hash_check.php - cp $(SRC_PATH)/installer/generate_build_files.php $(BUILD_PATH)/$(SRC_PATH)/generate_build_files.php; $(PHPCC) $(BUILD_PATH)/$(SRC_PATH)/generate_build_files.php; rm $(BUILD_PATH)/$(SRC_PATH)/generate_build_files.php + cp $(SRC_PATH)/installer/installer $(BUILD_PATH)/src/INSTALL + cp $(SRC_PATH)/installer/ncc.sh $(BUILD_PATH)/src/ncc.sh + cp $(SRC_PATH)/config/ncc.yaml $(BUILD_PATH)/src/default_config.yaml; + cp $(SRC_PATH)/config/ncc.yaml $(BUILD_PATH)/src/CLI/template_config.yaml; + cp $(SRC_PATH)/installer/extension $(BUILD_PATH)/src/extension + chmod +x $(BUILD_PATH)/src/INSTALL + cp LICENSE $(BUILD_PATH)/src/LICENSE + cp README.md $(BUILD_PATH)/src/README.md + cp $(SRC_PATH)/installer/hash_check.php $(BUILD_PATH)/src/hash_check.php; $(PHPCC) $(BUILD_PATH)/src/hash_check.php; rm $(BUILD_PATH)/src/hash_check.php + cp $(SRC_PATH)/installer/generate_build_files.php $(BUILD_PATH)/src/generate_build_files.php; $(PHPCC) $(BUILD_PATH)/src/generate_build_files.php; rm $(BUILD_PATH)/src/generate_build_files.php + mkdir -p $(BUILD_PATH)/src/repositories + cp -rf $(SRC_PATH)/default_repositories/*.json $(BUILD_PATH)/src/repositories tar: redist - cd $(BUILD_PATH)/src; tar -czvf ../ncc.tar.gz * + cd $(BUILD_PATH)/src; tar -czvf ../build.tar.gz * clean: rm -rf $(BUILD_PATH) diff --git a/README.md b/README.md index 9db1970..83fa984 100644 --- a/README.md +++ b/README.md @@ -4,38 +4,60 @@ Nosial Code Compiler is a program written in PHP designed to be a multi-purpose This program is a complete re-write of the now defunct [PHP Package Manager (PPM)](https://git.n64.cc/intellivoid/ppm) toolkit offering more features, security and proper code licensing and copyrighting for the components used for the project. -NCC Cannot compile, read or use PPM packages (.ppm) files or work with project sources designed to be built with PPM, however -a PPM extension may be built in the future to allow for backwards compatibility. +## Alpha Stage + +NCC is currently in alpha stage, meaning that it's not fully functional and may not work on your system. If you find any bugs +or issues please report them to the [GitHub Issue Tracker](https://git.n64.cc/intellivoid/ncc/issues). + +At the moment NCC is currently being used while developing other software, this serves as a test run to +improve on changes for the next version. + +## Version History + + - 1.0.0 Alpha - Initial release ([changelog](changelog/v1.0.0_alpha.md)) + +## Repository Mirrors + +The official repository for NCC is hosted on [GitLab](https://git.n64.cc/intellivoid/ncc), however, you can also find +mirrors of the repository mirrored on different platforms, including +community powered mirrors. (more to come) + + - [git.it-kuny.ch](https://git.it-kuny.ch) + - [git.martinvlba.eu](https://git.martinvlba.eu/Nosial/ncc) -## Notes +# Contributing - > While NCC has windows compatibility in mind, not all compiler extensions or features will work correctly. NCC is - > designed to be used in production in a Unix environment and Windows should only be used for development purposes. +We welcome contributions to NCC! If you have an idea for how to improve the project, please don't hesitate to reach out. +There are many ways to contribute, and we appreciate all forms of support. - > Compiler extensions requires their own set of dependencies to be met, for example Java compilers will require JDK +For more information on how to contribute, please read the [CONTRIBUTING.md](CONTRIBUTING.md) file. - > NCC is designed to run only on a PHP 8.0+ environment, compiler extensions can have support for different PHP versions. - > Third-party dependencies and included libraries has a dedicated namespace for `ncc` to avoid user land conflicts if - > the user wishes to install and use one of the same dependencies that NCC uses. +# Code of Conduct -## Authors - - Zi Xing Narrakas (netkas) <[netkas@n64.cc](mailto:netkas@64.cc)> +We are committed to maintaining a welcoming and inclusive environment for all contributors. Please read and follow our +[Code of Conduct](CODE_OF_CONDUCT.md). -## Special Thanks - - Marc Gutt (mgutt) <[marc@gutt.it](mailto:marc@gutt.it)> - - Debusschère Alexandre ([debuss](https://github.com/debuss)) +# Authors + +- Zi Xing Narrakas (netkas) <[netkas@n64.cc](mailto:netkas@64.cc)> + + +# Special Thanks + +- Marc Gutt (mgutt) <[marc@gutt.it](mailto:marc@gutt.it)> +- Debusschère Alexandre ([debuss](https://github.com/debuss)) + + +# Copyright + +- Copyright (c) 2022-2023, Nosial - All Rights Reserved -## Copyright -- Copyright (c) 2022-2022, Nosial - All Rights Reserved -- Copyright (c) 2004-2022, Fabien Potencier -- Copyright (c) 2010, dealnews.com, Inc. All rights reserved. -- Copyright (c) 2013 Austin Hyde -- Copyright (C) 2009-2016 Laurent Jouanneau -- Copyright (c) 2011, Nikita Popov -- Copyright (c) 2010-2016 Arne Blankerts and Contributors # Licenses -Multiple licenses can be found at [LICENSE](LICENSE) \ No newline at end of file +NCC is licensed under the MIT License, see [LICENSE](LICENSE) for more information. + +Multiple licenses for the open source components used in this +project can be found at [LICENSE](LICENSES) \ No newline at end of file diff --git a/assets/ncc_cli.png b/assets/ncc_cli.png new file mode 100644 index 0000000..64fb3c4 Binary files /dev/null and b/assets/ncc_cli.png differ diff --git a/changelog/v1.0.0_alpha.md b/changelog/v1.0.0_alpha.md new file mode 100644 index 0000000..814a5f0 --- /dev/null +++ b/changelog/v1.0.0_alpha.md @@ -0,0 +1,18 @@ +# v1.0.0 Alpha + +First release of NCC (Nosial Code Compiler) in alpha stage. + +## Alpha Stage + +NCC is in its alpha stage, meaning that it's not fully +functional and may not work on your system. If you find +any bugs or issues please report them to the +[Issue Tracker](https://git.n64.cc/intellivoid/ncc/issues). + +At the moment NCC is currently being used while developing +other software, this serves as a test run to improve on +changes for the next version. + +## Changelog + + - Initial release \ No newline at end of file diff --git a/docs/building_ncc.md b/docs/building_ncc.md deleted file mode 100644 index 2d3b6f6..0000000 --- a/docs/building_ncc.md +++ /dev/null @@ -1,94 +0,0 @@ -# Building NCC from source - -Building NCC from source is easy with very few requirements -to start building. At the moment ncc can only be debugged -or tested by building a redistributable source and -installing it. - -## Requirements to build - - - php8.0+ - - php-mbstring - - php-ctype - - php-tokenizer *(or php-common)* - - make - - phpab - - tar *(optional)* - -## Installing phpab - -phpab is also known as [PHP Autoload Builder](https://github.com/theseer/Autoload), -phpab is an open source tool used for creating autoload -files, ncc needs this tool in order to generate it's -autoload files whenever there's any changes to its source -code. - -This tool is only required for building and or creating a -redistributable package of ncc. This component is not -required to be installed to use ncc. - -for some components that require static loading, ncc will -automatically load it using it's own -[autoloader](../src/autoload/autoload.php) - -The recommended way to install phpab is by using [phive](https://phar.io/), -if you don't have phive installed you can install it by -running these commands in your terminal (from the official documentation) - -```shell -wget -O phive.phar https://phar.io/releases/phive.phar -wget -O phive.phar.asc https://phar.io/releases/phive.phar.asc -gpg --keyserver hkps://keys.openpgp.org --recv-keys 0x9D8A98B29B2D5D79 -gpg --verify phive.phar.asc phive.phar -chmod +x phive.phar -sudo mv phive.phar /usr/local/bin/phive -``` - -Once phive is installed, you can run the final command to -install phpab -```shell -phive install phpab -``` - -**Note:** Optionally, you may want to have `phab` available in your -`$PATH`, this can be done with this command. *(Replace `x.xx.x` with your -version number)* - -```shell -ln -s /home/user/.phive/phars/phpab-x.xx.x.phar /usr/bin/phpab -``` - -## Building NCC - -First, navigate to the main directory of NCC's source code -where the [Makefile](../Makefile) is present. If you -already attempted to or had built ncc before, it's -recommended to use `make clean` before building. - - -### Redist - -Running `redist` from the Makefile will generate all the -required autoloaders for ncc and move all the required -files into one redistributable source folder under a -directory called `build/src` - -```shell -make redist -``` - - -### Tar - -Running `tar` will run redist before packaging the -redistributable source into a tar.gz file that can -be distributed to other machines, this process is not -a requirement. - -```shell -make tar -``` - -Once you have a populated `build/src` folder, you can -simply run execute the `installer` file to install your -build of ncc onto the running machine. \ No newline at end of file diff --git a/docs/package_name.md b/docs/package_name.md deleted file mode 100644 index 0b4586e..0000000 --- a/docs/package_name.md +++ /dev/null @@ -1,43 +0,0 @@ -# Naming a package - -**Updated on Tuesday, July 26, 2022** - -NCC Follows the same naming convention as Java's naming -convention. The purpose of naming a package this way is -to easily create a "Name" of the package, this string -of information contains - - - The developer/organization behind the package - - The package name itself - - -# Naming conventions - -Package names are written in all lower-case due to the -fact that some operating systems treats file names -differently, for example on Linux `Aa.txt` and `aa.txt` -are two entirely different file names because of the -capitalization and on Windows it's treated as the same -file name. - -Organizations or small developers use their domain name -in reverse to begin their package names, for example -`net.nosial.example` is a package named `example` -created by a programmer at `nosial.net` - -Just like the Java naming convention, to avoid conflicts -of the same package name developers can use something -different, for example as pointed out in Java's package -naming convention developers can instead use something -like a region to name packages, for example -`net.nosial.region.example` - - -# References - -For Java's package naming conventions see -[Naming a Package](https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html) -from the Oracle's Java documentation resource, as the -same rules apply to NCC except for *some* illegal naming -conventions such as packages not being able to begin -with `int` or numbers \ No newline at end of file diff --git a/docs/project_configuration/execution_policy.md b/docs/project_configuration/execution_policy.md deleted file mode 100644 index f5b851b..0000000 --- a/docs/project_configuration/execution_policy.md +++ /dev/null @@ -1,151 +0,0 @@ -# Execution Policies - -**Updated on Sunday, November 20, 2022** - -An execution policy is a policy defined in the Project -configuration file (`project.json`) that can be used -to execute a script or program in any stage of the package - -For instance, you can have a script that is executed before -the build process starts, or in different installation stages -when the user is installing your package you can have a unit -run before or after the installation/uninstallation process -starts.# - -Use cases such as this allows you to properly implement -and control your program's files & assets that are not -handled by NCC's compiler extensions. - -## Table of Contents - - -* [Execution Policies](#execution-policies) - * [Table of Contents](#table-of-contents) - * [JSON Example](#json-example) - * [ExecutionPolicy Object](#executionpolicy-object) - * [Object Properties](#object-properties) - * [JSON Example](#json-example) - * [ExecutionConfiguration Object](#executionconfiguration-object) - * [Object Properties](#object-properties) - * [JSON Example](#json-example) - * [ExitHandler Object](#exithandler-object) - * [Object Properties](#object-properties) - * [JSON Example](#json-example) - - - -## JSON Example - -```json -{ - "execution_policies": { - "main": { - "runner": "php", - "message": "Running main %ASSEMBLY.PACKAGE%", - "exec": { - "target": "scripts/main.php", - "working_directory": "%INSTALL_PATH.SRC%", - "silent": false - } - }, - "hello_world": { - "runner": "shell", - "message": "Running HTOP", - "options": { - "htop": null - }, - "exec": { - "tty": true - } - } - } -} -``` - ------------------------------------------------------------- - -## ExecutionPolicy Object - -Execution Policies for your project **must** have unique -names, because they way you tell NCC to execute these -policies is by referencing their name in the configuration. - -Invalid names/undefined policies will raise errors when -building the project - -### Object Properties - -| Property Name | Value Type | Example Value | Description | -|-----------------|---------------------------------|----------------------|--------------------------------------------------------------------------------------------| -| `runner` | string | bash | The name of a supported runner instance, see runners in this document | -| `message` | string, null | Starting foo_bar ... | *Optional* the message to display before running the execution policy | -| `exec` | ExecutionConfiguration | N/A | The configuration object that tells how the runner should execute the process | -| `exit_handlers` | ExitHandlersConfiguration, null | N/A | *Optional* Exit Handler Configurations that tells NCC how to handle exits from the process | - -### JSON Example - -```json -{ - "name": "foo_bar", - "runner": "bash", - "message": "Running foo_bar ...", - "exec": null, - "exit_handlers": null -} -``` - ------------------------------------------------------------- - -## ExecutionConfiguration Object - -### Object Properties - -| Property Name | Value Type | Example Value | Description | -|---------------------|-------------------|---------------------------------|------------------------------------------------------------------------| -| `target` | `string` | scripts/foo_bar.bash | The target file to execute | -| `working_directory` | `string`, `null` | %INSTALL_PATH.SRC% | *optional* The working directory to execute the process in | -| `options` | `array`, `null` | {"run": null, "log": "verbose"} | Commandline Parameters to pass on to the target or process | -| `silent` | `boolean`, `null` | False | Indicates if the target should run silently, by default this is false. | -| `tty` | `boolean`, `null` | False | Indicates if the target should run in TTY mode | -| `timeout` | `integer`, `null` | 60 | The amount of seconds to wait before the process is killed | - -### JSON Example - -```json -{ - "target": "scripts/foo_bar.bash", - "working_directory": "%INSTALL_PATH.SRC%", - "options": {"run": null, "log": "verbose"}, - "silent": false, - "tty": false, - "timeout": 10 -} -``` - - ------------------------------------------------------------- - -## ExitHandler Object - -An exit handler is executed once the specified exit code is -returned or the process exits with an error or normally, if -an exit handler is specified it will be executed. - -### Object Properties - -| Property Name | Value Type | Example Value | Description | -|---------------|--------------------|---------------|------------------------------------------------------------------------------| -| `message` | `string` | Hello World! | The message to display when the exit handler is triggered | -| `end_process` | `boolean`, `null` | False | *optional* Kills the process after this exit handler is triggered | -| `run` | `string`, `null` | `null` | *optional* A execution policy to execute once this exit handler is triggered | -| `exit_code` | `int`, `null` | 1 | The exit code that triggers this exit handler | -### JSON Example - -```json -{ - "message": "Hello World", - "end_process": false, - "run": null, - "exit_code": 1 -} -``` \ No newline at end of file diff --git a/src/config/ncc.yaml b/src/config/ncc.yaml index 0b713ba..36c6488 100644 --- a/src/config/ncc.yaml +++ b/src/config/ncc.yaml @@ -19,12 +19,6 @@ ncc: php: # The main executable path for PHP that NCC should use executable_path: "/usr/bin/php" - runtime: - # Whether to initialize NCC when running `require('ncc');` - initialize_on_require: true - - # if NCC should handle fatal exceptions during execution - handle_exceptions: true git: # if git is enabled or not @@ -55,10 +49,10 @@ composer: quiet: false # Disable ANSI output - no_ansi: false + no_ansi: true # Do not ask any interactive question - no_interaction: false + no_interaction: true # Display timing and memory usage information profile: false diff --git a/src/default_repositories/custom_repositories.json b/src/default_repositories/custom_repositories.json new file mode 100644 index 0000000..aa1a562 --- /dev/null +++ b/src/default_repositories/custom_repositories.json @@ -0,0 +1,6 @@ +[ + "gitgud.json", + "github.json", + "gitlab.json", + "n64.json" +] \ No newline at end of file diff --git a/src/default_repositories/gitgud.json b/src/default_repositories/gitgud.json new file mode 100644 index 0000000..83f904e --- /dev/null +++ b/src/default_repositories/gitgud.json @@ -0,0 +1,6 @@ +{ + "name": "gitgud", + "type": "gitlab", + "host": "gitgud.io", + "ssl": true +} \ No newline at end of file diff --git a/src/default_repositories/github.json b/src/default_repositories/github.json new file mode 100644 index 0000000..f05cc8c --- /dev/null +++ b/src/default_repositories/github.json @@ -0,0 +1,6 @@ +{ + "name": "github", + "type": "github", + "host": "api.github.com", + "ssl": true +} \ No newline at end of file diff --git a/src/default_repositories/gitlab.json b/src/default_repositories/gitlab.json new file mode 100644 index 0000000..2694fbd --- /dev/null +++ b/src/default_repositories/gitlab.json @@ -0,0 +1,6 @@ +{ + "name": "gitlab", + "type": "gitlab", + "host": "gitlab.com", + "ssl": true +} \ No newline at end of file diff --git a/src/default_repositories/n64.json b/src/default_repositories/n64.json new file mode 100644 index 0000000..ac57ee8 --- /dev/null +++ b/src/default_repositories/n64.json @@ -0,0 +1,6 @@ +{ + "name": "n64", + "type": "gitlab", + "host": "git.n64.cc", + "ssl": true +} \ No newline at end of file diff --git a/src/installer/extension b/src/installer/extension index aaff950..2ccc72b 100644 --- a/src/installer/extension +++ b/src/installer/extension @@ -1,8 +1,19 @@ exists(PathFinder::getConfigurationFile())) { - $config_backup = IO::fread(PathFinder::getConfigurationFile()); + $config_backup = Yaml::parseFile(PathFinder::getConfigurationFile()); } } catch (Exception $e) @@ -736,33 +737,73 @@ // Create/Update configuration file $config_obj = Yaml::parseFile(__DIR__ . DIRECTORY_SEPARATOR . 'default_config.yaml'); - // Update the old configuration - if($config_backup !== null) + if(!function_exists('array_replace_recursive')) { - $old_config_obj = Yaml::parse($config_backup); - foreach($old_config_obj as $section => $value) + /** + * @param $array + * @param $array1 + * @return array|mixed + * @author + * @noinspection PhpMissingReturnTypeInspection + */ + function array_replace_recursive($array, $array1) { - if(isset($config_obj[$section])) + // handle the arguments, merge one by one + $args = func_get_args(); + $array = $args[0]; + if (!is_array($array)) { - foreach($value as $section_item => $section_value) + return $array; + } + for ($i = 1; $i < count($args); $i++) + { + if (is_array($args[$i])) { - if(!isset($config_obj[$section][$section_item])) - { - $config_obj[$section][$section_item] = $section_value; - } + $array = recurse($array, $args[$i]); } } - else - { - $config_obj[$section] = $value; - } + return $array; } } + if(!function_exists('recurse')) + { + /** + * @param $array + * @param $array1 + * @return mixed + * @author + * @noinspection PhpMissingReturnTypeInspection + */ + function recurse($array, $array1) + { + foreach ($array1 as $key => $value) + { + // create new key in $array, if it is empty or not an array + /** @noinspection PhpConditionAlreadyCheckedInspection */ + if (!isset($array[$key]) || (isset($array[$key]) && !is_array($array[$key]))) + { + $array[$key] = array(); + } + + // overwrite the value in the base array + if (is_array($value)) + { + $value = recurse($array[$key], $value); + } + $array[$key] = $value; + } + return $array; + } + } + + + if($config_backup !== null) + $config_obj = array_replace_recursive($config_obj, $config_backup); + if($config_backup == null) { Console::out('Generating ncc.yaml'); - } else { @@ -779,6 +820,57 @@ return; } + if($NCC_FILESYSTEM->exists(__DIR__ . DIRECTORY_SEPARATOR . 'repositories')) + { + if(!$NCC_FILESYSTEM->exists(__DIR__ . DIRECTORY_SEPARATOR . 'repositories' . DIRECTORY_SEPARATOR . 'custom_repositories.json')) + return; + + try + { + $custom_repositories = Functions::loadJsonFile(__DIR__ . DIRECTORY_SEPARATOR . 'repositories' . DIRECTORY_SEPARATOR . 'custom_repositories.json', Functions::FORCE_ARRAY); + } + catch(Exception $e) + { + $custom_repositories = null; + Console::outWarning(sprintf('Failed to load custom repositories: %s', $e->getMessage())); + } + + if($custom_repositories !== null) + { + $source_manager = new RemoteSourcesManager(); + foreach($custom_repositories as $repository) + { + $repo_path = __DIR__ . DIRECTORY_SEPARATOR . 'repositories' . DIRECTORY_SEPARATOR . $repository; + if($NCC_FILESYSTEM->exists($repo_path)) + { + try + { + $definedEntry = DefinedRemoteSource::fromArray(Functions::loadJsonFile($repo_path, Functions::FORCE_ARRAY)); + if(!$source_manager->getRemoteSource($definedEntry->Name)) + $source_manager->addRemoteSource($definedEntry); + } + catch(Exception $e) + { + Console::outWarning(sprintf('Failed to load custom repository %s: %s', $repository, $e->getMessage())); + } + } + else + { + Console::outWarning(sprintf('Failed to load custom repository %s, file does not exist', $repository)); + } + } + + try + { + $source_manager->save(); + } + catch (\ncc\Exceptions\IOException $e) + { + Console::outWarning(sprintf('Failed to save sources: %s', $e->getMessage())); + } + } + } + Console::out('NCC version: ' . NCC_VERSION_NUMBER . ' has been successfully installed'); Console::out('For licensing information see \'' . $NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'LICENSE\' or run \'ncc help --license\''); diff --git a/src/ncc/Abstracts/AuthenticationSource.php b/src/ncc/Abstracts/AuthenticationSource.php deleted file mode 100644 index c5b29f8..0000000 --- a/src/ncc/Abstracts/AuthenticationSource.php +++ /dev/null @@ -1,12 +0,0 @@ -getPackageLock()->getPackage($package); + } + catch(Exception $e) + { + Console::outException('Package ' . $package . ' is not installed', $e, 1); + return; + } + + try + { + $version_entry = $package_entry->getVersion($version); + } + catch(Exception $e) + { + Console::outException('Version ' . $version . ' is not installed', $e, 1); + return; + } + + try + { + $units = $execution_pointer_manager->getUnits($package_entry->Name, $version_entry->Version); + } + catch(Exception $e) + { + Console::outException(sprintf('Cannot load execution units for package \'%s\'', $package), $e, 1); + return; + } + + if(!in_array($unit_name, $units)) + { + Console::outError(sprintf('Unit \'%s\' is not configured for package \'%s\'', $unit_name, $package), true, 1); + return; + } + + $options = []; + + if($set_args != null) + { + global $argv; + $args_index = array_search('--exec-args', $argv); + $options = array_slice($argv, $args_index + 1); + } + + try + { + exit($execution_pointer_manager->executeUnit($package_entry->Name, $version_entry->Version, $unit_name, $options)); + } + catch(Exception $e) + { + Console::outException(sprintf('Cannot execute execution point \'%s\' in package \'%s\'', $unit_name, $package), $e, 1); + return; + } + } + + /** + * Displays the main options section + * + * @return void + */ + private static function displayOptions(): void + { + $options = [ + new CliHelpSection(['help'], 'Displays this help menu about the value command'), + new CliHelpSection(['exec', '--package'], '(Required) The package to execute'), + new CliHelpSection(['--exec-version'], '(default: latest) The version of the package to execute'), + new CliHelpSection(['--exec-unit'], '(default: main) The unit point of the package to execute'), + new CliHelpSection(['--exec-args'], '(optional) Anything past this point will be passed to the execution unit'), + ]; + + $options_padding = Functions::detectParametersPadding($options) + 4; + + Console::out('Usage: ncc exec --package [options] [arguments]'); + Console::out('Options:' . PHP_EOL); + foreach($options as $option) + { + Console::out(' ' . $option->toString($options_padding)); + } + + Console::out(PHP_EOL . 'Arguments:' . PHP_EOL); + Console::out(' The arguments to pass to the program'); + Console::out(PHP_EOL . 'Example Usage:' . PHP_EOL); + Console::out(' ncc exec --package com.example.program'); + Console::out(' ncc exec --package com.example.program --exec-version 1.0.0'); + Console::out(' ncc exec --package com.example.program --exec-version 1.0.0 --exec-unit setup'); + Console::out(' ncc exec --package com.example.program --exec-args --foo --bar --extra=test'); + } + } \ No newline at end of file diff --git a/src/ncc/CLI/CredentialMenu.php b/src/ncc/CLI/CredentialMenu.php deleted file mode 100644 index a82336f..0000000 --- a/src/ncc/CLI/CredentialMenu.php +++ /dev/null @@ -1,72 +0,0 @@ -toString($options_padding) . PHP_EOL); - } - print(PHP_EOL); - } - } \ No newline at end of file diff --git a/src/ncc/CLI/HelpMenu.php b/src/ncc/CLI/HelpMenu.php index 140bae4..8381429 100644 --- a/src/ncc/CLI/HelpMenu.php +++ b/src/ncc/CLI/HelpMenu.php @@ -1,9 +1,33 @@ toString($options_padding)); - } + ]); } /** @@ -67,20 +86,14 @@ */ private static function displayManagementCommands(): void { - $commands = [ + Console::out('Management Commands:'); + Console::outHelpSections([ new CliHelpSection(['project'], 'Manages the current project'), new CliHelpSection(['package'], 'Manages the package system'), - new CliHelpSection(['cache'], 'Manages the system cache'), - new CliHelpSection(['credential'], 'Manages credentials'), + new CliHelpSection(['cred'], 'Manages credentials'), new CliHelpSection(['config'], 'Changes NCC configuration values'), - ]; - $commands_padding = \ncc\Utilities\Functions::detectParametersPadding($commands) + 2; - - Console::out('Management Commands:'); - foreach($commands as $command) - { - Console::out(' ' . $command->toString($commands_padding)); - } + new CliHelpSection(['source'], 'Manages remote sources'), + ]); } /** @@ -90,35 +103,10 @@ */ private static function displayMainCommands(): void { - $commands = [ - new CliHelpSection(['build'], 'Builds the current project'), - new CliHelpSection(['main'], 'Executes the main entrypoint of a package') - ]; - $commands_padding = \ncc\Utilities\Functions::detectParametersPadding($commands) + 2; - Console::out('Commands:'); - foreach($commands as $command) - { - Console::out(' ' . $command->toString($commands_padding)); - } - } - - /** - * Displays the main commands section - * - * @return void - */ - private static function displayExtensions(): void - { - $extensions = [ - new CliHelpSection(['exphp'], 'The PHP compiler extension') - ]; - $extensions_padding = \ncc\Utilities\Functions::detectParametersPadding($extensions) + 2; - - Console::out('Extensions:'); - foreach($extensions as $command) - { - Console::out(' ' . $command->toString($extensions_padding)); - } + Console::outHelpSections([ + new CliHelpSection(['build'], 'Builds the current project'), + new CliHelpSection(['exec'], 'Executes the main entrypoint of a package') + ]); } } \ No newline at end of file diff --git a/src/ncc/CLI/Main.php b/src/ncc/CLI/Main.php index 793a8fc..5ce5ad0 100644 --- a/src/ncc/CLI/Main.php +++ b/src/ncc/CLI/Main.php @@ -1,4 +1,24 @@ getMessage()); + } + } + } \ No newline at end of file diff --git a/src/ncc/CLI/ConfigMenu.php b/src/ncc/CLI/Management/ConfigMenu.php similarity index 79% rename from src/ncc/CLI/ConfigMenu.php rename to src/ncc/CLI/Management/ConfigMenu.php index ec7bc2f..2afc0aa 100644 --- a/src/ncc/CLI/ConfigMenu.php +++ b/src/ncc/CLI/Management/ConfigMenu.php @@ -1,6 +1,26 @@ getVault()->getEntry($name); + + if($entry === null) + { + Console::out('Entry not found.', true, 1); + return; + } + + if($entry->isEncrypted()) + { + $tries = 0; + + while(true) + { + try + { + $password = Console::passwordInput('Password/Secret: '); + if (!$entry->unlock($password)) + { + $tries++; + if ($tries >= 3) + { + Console::outError('Too many failed attempts.', true, 1); + return; + } + + Console::outError('Invalid password.', true, 1); + } + else + { + Console::out('Authentication successful.'); + return; + } + } + catch (RuntimeException $e) + { + Console::outException('Cannot unlock entry.', $e, 1); + return; + } + } + } + + else + { + Console::out('Authentication always successful, entry is not encrypted.'); + } + } + + /** + * Prints the list of entries in the vault + * + * @return void + */ + public static function listEntries(): void + { + $credential_manager = new CredentialManager(); + $entries = $credential_manager->getVault()->getEntries(); + + if(count($entries) === 0) + { + Console::out('No entries found.'); + return; + } + + Console::out('Entries:'); + foreach($entries as $entry) + { + Console::out(sprintf(' - %s (%s)', $entry->getName(), $entry->isEncrypted() ? ' (encrypted)' : '')); + } + + Console::out('Total: ' . count($entries)); + } + + /** + * @param $args + * @return void + */ + public static function addEntry($args): void + { + $ResolvedScope = Resolver::resolveScope(); + + if($ResolvedScope !== Scopes::System) + Console::outError('Insufficient permissions to add entries'); + + // Really dumb-proofing this + $name = $args['alias'] ?? $args['name'] ?? null; + $auth_type = $args['auth-type'] ?? $args['auth'] ?? null; + $username = $args['username'] ?? $args['usr'] ?? null; + $password = $args['password'] ?? $args['pwd'] ?? null; + $token = $args['token'] ?? $args['pat'] ?? $args['private-token'] ?? null; + $encrypt = !isset($args['no-encryption']); + + if($name === null) + $name = Console::getInput('Enter a name for the entry: '); + + if($auth_type === null) + $auth_type = Console::getInput('Enter the authentication type (login or pat): '); + + if($auth_type === 'login') + { + if($username === null) + $username = Console::getInput('Username: '); + + if($password === null) + $password = Console::passwordInput('Password: '); + } + elseif($auth_type === 'pat') + { + if($token === null) + $token = Console::passwordInput('Token: '); + } + else + { + Console::outError('Invalid authentication type'); + } + + if($name === null) + { + Console::outError('You must specify a name for the entry (alias, name)', true, 1); + return; + } + + if($auth_type === null) + { + Console::outError('You must specify an authentication type for the entry (auth-type, auth)', true, 1); + return; + } + + $encrypt = Functions::cbool($encrypt); + + switch($auth_type) + { + case 'login': + + if($username === null) + { + Console::outError('You must specify a username for the entry (username, usr)', true, 1); + return; + } + if($password === null) + { + Console::outError('You must specify a password for the entry (password, pwd)', true, 1); + return; + } + + $pass_object = new UsernamePassword(); + $pass_object->setUsername($username); + $pass_object->setPassword($password); + + break; + + case 'pat': + + if($token === null) + { + Console::outError('You must specify a token for the entry (token, pat, private-token)', true, 1); + return; + } + + $pass_object = new AccessToken(); + $pass_object->setAccessToken($token); + + break; + + default: + Console::outError('Invalid authentication type specified', true, 1); + return; + } + + $credential_manager = new CredentialManager(); + if(!$credential_manager->getVault()->addEntry($name, $pass_object, $encrypt)) + { + Console::outError('Failed to add entry, entry already exists.', true, 1); + return; + } + + try + { + $credential_manager->saveVault(); + } + catch(Exception $e) + { + Console::outException('Failed to save vault', $e, 1); + return; + } + + Console::out('Successfully added entry', true, 0); + } + + /** + * Removes an existing entry from the vault. + * + * @param $args + * @return void + */ + private static function removeEntry($args): void + { + $ResolvedScope = Resolver::resolveScope(); + + if($ResolvedScope !== Scopes::System) + Console::outError('Insufficient permissions to remove entries'); + + $name = $args['alias'] ?? $args['name'] ?? null; + + if($name === null) + $name = Console::getInput('Enter the name of the entry to remove: '); + + $credential_manager = new CredentialManager(); + if(!$credential_manager->getVault()->deleteEntry($name)) + { + Console::outError('Failed to remove entry, entry does not exist.', true, 1); + return; + } + + try + { + $credential_manager->saveVault(); + } + catch(Exception $e) + { + Console::outException('Failed to save vault', $e, 1); + return; + } + + Console::out('Successfully removed entry', true, 0); + } + + /** + * Displays the main options section + * + * @return void + */ + private static function displayOptions(): void + { + Console::out('Usage: ncc cred {command} [options]'); + Console::out('Options:'); + Console::outHelpSections([ + new CliHelpSection(['help'], 'Displays this help menu about the value command'), + new CliHelpSection(['add'], 'Adds a new entry to the vault (See below)'), + new CliHelpSection(['remove', '--name'], 'Removes an entry from the vault'), + new CliHelpSection(['list'], 'Lists all entries in the vault'), + ]); + Console::out((string)null); + + Console::out('If you are adding a new entry, you can run the add command in interactive mode'); + Console::out('or you can specify the options below' . PHP_EOL); + + Console::out('Add Options:'); + Console::outHelpSections([ + new CliHelpSection(['--name'], 'The name of the entry'), + new CliHelpSection(['--auth-type', '--auth'], 'The type of authentication (login, pat)'), + new CliHelpSection(['--no-encryption'], 'Omit encryption to the entry (By default it\'s encrypted)', true), + ]); + + Console::out(' login authentication type options:'); + Console::outHelpSections([ + new CliHelpSection(['--username', '--usr'], 'The username for the entry'), + new CliHelpSection(['--password', '--pwd'], 'The password for the entry'), + ]); + + Console::out(' pat authentication type options:'); + Console::outHelpSections([ + new CliHelpSection(['--token', '--pat',], 'The private access token for the entry', true), + ]); + + Console::out('Authentication Types:'); + Console::out(' login'); + Console::out(' pat' . PHP_EOL); + + Console::out('Examples:'); + Console::out(' ncc cred add --alias "My Alias" --auth-type login --username "myusername" --password "mypassword"'); + Console::out(' ncc cred add --alias "My Alias" --auth-type pat --token "mytoken" --no-encryption'); + Console::out(' ncc cred remove --alias "My Alias"'); + } + } \ No newline at end of file diff --git a/src/ncc/CLI/PackageManagerMenu.php b/src/ncc/CLI/Management/PackageManagerMenu.php similarity index 65% rename from src/ncc/CLI/PackageManagerMenu.php rename to src/ncc/CLI/Management/PackageManagerMenu.php index a66d117..5dfbc49 100644 --- a/src/ncc/CLI/PackageManagerMenu.php +++ b/src/ncc/CLI/Management/PackageManagerMenu.php @@ -1,15 +1,36 @@ MagicBytes?->toArray() ?? []), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + Console::out('header: ' . json_encode(($package->Header?->toArray() ?? []), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + Console::out('assembly: ' . json_encode(($package->Assembly?->toArray() ?? []), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + Console::out('main: ' . ($package->MainExecutionPolicy ?? 'N/A')); + Console::out('installer: ' . ($package->Installer?->toArray() ?? 'N/A')); + + if($package->Dependencies !== null && count($package->Dependencies) > 0) + { + Console::out('dependencies:'); + foreach($package->Dependencies as $dependency) + { + Console::out(' - ' . json_encode($dependency->toArray(), JSON_UNESCAPED_SLASHES)); + } + } + else + { + Console::out('dependencies: N/A'); + } + + if($package->ExecutionUnits !== null && count($package->ExecutionUnits) > 0) + { + Console::out('execution_units:'); + foreach($package->ExecutionUnits as $unit) + { + Console::out(' - ' . json_encode($unit->toArray(), JSON_UNESCAPED_SLASHES)); + } + } + else + { + Console::out('execution_units: N/A'); + } + + if($package->Resources !== null && count($package->Resources) > 0) + { + Console::out('resources:'); + foreach($package->Resources as $resource) + { + Console::out(' - ' . sprintf('%s - (%s)', $resource->Name, Functions::b2u(strlen($resource->Data)))); + } + } + else + { + Console::out('resources: N/A'); + } + + if($package->Components !== null && count($package->Components) > 0) + { + Console::out('components:'); + foreach($package->Components as $component) + { + Console::out(' - ' . sprintf('#%s %s - %s', $component->DataType, $component->Name, json_encode(($component->Flags ?? []), JSON_UNESCAPED_SLASHES))); + } + } + else + { + Console::out('components: N/A'); + } + + exit(0); + } + /** * Displays all installed packages * @@ -156,6 +276,7 @@ $package_version = $package_manager->getPackageVersion($package, $version); if($package_version == null) continue; + Console::out(sprintf('%s=%s (%s)', Console::formatColor($package, ConsoleColors::LightGreen), Console::formatColor($version, ConsoleColors::LightMagenta), @@ -165,9 +286,10 @@ catch(Exception $e) { unset($e); - Console::out(sprintf('%s=%s', + Console::out(sprintf('%s=%s (%s)', Console::formatColor($package, ConsoleColors::LightGreen), - Console::formatColor($version, ConsoleColors::LightMagenta) + Console::formatColor($version, ConsoleColors::LightMagenta), + Console::formatColor('N/A', ConsoleColors::LightRed) )); } } @@ -191,33 +313,66 @@ return; } - $path = $package; - $parsed_source = new RemotePackageInput($path); - if($parsed_source->Vendor !== null && $parsed_source->Package !== null) + // check if authentication is provided + $entry_arg = ($args['auth'] ?? null); + $credential_manager = new CredentialManager(); + + if($entry_arg !== null) { - if($parsed_source->Source == null) + $credential = $credential_manager->getVault()->getEntry($entry_arg); + + if($credential == null) { - Console::outError('No source specified', true, 1); + Console::outError(sprintf('Unknown credential entry \'%s\'', $entry_arg), true, 1); return; } + } + else + { + $credential = null; + } - switch($parsed_source->Source) + if($credential !== null && !$credential->isCurrentlyDecrypted()) + { + // Try 3 times + for($i = 0; $i < 3; $i++) { - case RemoteSource::Composer: - try - { - $path = ComposerSource::fetch($parsed_source); - break; - } - catch(Exception $e) - { - Console::outException(sprintf('Failed to fetch package %s', $package), $e, 1); - return; - } - - default: - Console::outError('Cannot install package from source: ' . $parsed_source->Source, true, 1); + try + { + $credential->unlock(Console::passwordInput(sprintf('Enter Password for %s: ', $credential->getName()))); + } + catch (RuntimeException $e) + { + Console::outException(sprintf('Failed to unlock credential %s', $credential->getName()), $e, 1); return; + } + + if($credential->isCurrentlyDecrypted()) + break; + + Console::outWarning(sprintf('Invalid password, %d attempts remaining', 2 - $i)); + } + + if(!$credential->isCurrentlyDecrypted()) + { + Console::outError('Failed to unlock credential', true, 1); + return; + } + } + + $path = $package; + $parsed_source = new RemotePackageInput($path); + + if($parsed_source->Vendor !== null && $parsed_source->Package !== null && $parsed_source->Source !== null) + { + try + { + $path = $package_manager->fetchFromSource($parsed_source->toString(), $credential); + } + catch (Exception $e) + { + Console::outException('Failed to fetch package from source', $e, 1); + return; } } @@ -230,6 +385,18 @@ $user_confirmation = (bool)($args['y'] ?? $args['Y']); } + $installer_options = []; + + if((Functions::cbool($args['skip-dependencies'] ?? false))) + { + $installer_options[] = InstallPackageOptions::SkipDependencies; + } + + if(Functions::cbool($args['reinstall'] ?? false)) + { + $installer_options[] = InstallPackageOptions::Reinstall; + } + try { $package = Package::load($path); @@ -261,38 +428,39 @@ Console::out(' Trademark: ' . Console::formatColor($package->Assembly->Trademark, ConsoleColors::LightGreen)); Console::out((string)null); - if(count($package->Dependencies) > 0) + if(count($package->Dependencies) > 0 && !in_array(InstallPackageOptions::Reinstall, $installer_options)) { $dependencies = []; foreach($package->Dependencies as $dependency) { - $require_dependency = true; + $require_dependency = false; - try - { - $dependency_package = $package_manager->getPackage($dependency->Name); - } - catch (PackageLockException $e) - { - unset($e); - $dependency_package = null; - } - - if($dependency_package !== null) + if(!in_array(InstallPackageOptions::SkipDependencies, $installer_options)) { try { - $dependency_version = $dependency_package->getVersion($dependency->Version); + $dependency_package = $package_manager->getPackage($dependency->Name); } - catch (VersionNotFoundException $e) + catch (PackageLockException $e) { unset($e); - $dependency_version = null; + $dependency_package = null; } - if($dependency_version !== null) + if($dependency_package !== null) { - $require_dependency = false; + try + { + $dependency_version = $dependency_package->getVersion($dependency->Version); + } + catch (VersionNotFoundException $e) + { + unset($e); + $dependency_version = null; + } + + if($dependency_version == null) + $require_dependency = true; } } @@ -305,8 +473,11 @@ } } - Console::out('The following dependencies will be installed:'); - Console::out(sprintf('%s', implode(PHP_EOL, $dependencies))); + if($dependencies !== null && count($dependencies) > 0) + { + Console::out('The package requires the following dependencies:'); + Console::out(sprintf('%s', implode(PHP_EOL, $dependencies))); + } } Console::out(sprintf('Extension: %s', @@ -326,11 +497,13 @@ if(!$user_confirmation) $user_confirmation = Console::getBooleanInput(sprintf('Do you want to install %s', $package->Assembly->Package)); + + if($user_confirmation) { try { - $package_manager->install($path); + $package_manager->install($path, $credential, $installer_options); Console::out(sprintf('Package %s installed successfully', $package->Assembly->Package)); } catch(Exception $e) @@ -382,7 +555,6 @@ $version_entry = null; if($version_entry !== null && $package_entry !== null) - /** @noinspection PhpUnhandledExceptionInspection */ /** @noinspection PhpRedundantOptionalArgumentInspection */ $version_entry = $package_entry->getVersion($version_entry, false); @@ -490,9 +662,12 @@ new CliHelpSection(['list'], 'Lists all installed packages on the system'), new CliHelpSection(['install', '--package', '-p'], 'Installs a specified NCC package'), new CliHelpSection(['install', '--package', '-p', '--version', '-v'], 'Installs a specified NCC package version'), + new CliHelpSection(['install', '-p', '--skip-dependencies'], 'Installs a specified NCC package but skips the installation of dependencies'), + new CliHelpSection(['install', '-p', '--reinstall'], 'Installs a specified NCC package, reinstall if already installed'), new CliHelpSection(['uninstall', '--package', '-p'], 'Uninstalls a specified NCC package'), new CliHelpSection(['uninstall', '--package', '-p', '--version', '-v'], 'Uninstalls a specified NCC package version'), new CliHelpSection(['uninstall-all'], 'Uninstalls all packages'), + new CliHelpSection(['sdc', '--package', '-p'], '(Debug) Semi-decompiles a specified NCC package and prints the result to the console'), ]; $options_padding = Functions::detectParametersPadding($options) + 4; diff --git a/src/ncc/CLI/ProjectMenu.php b/src/ncc/CLI/Management/ProjectMenu.php similarity index 80% rename from src/ncc/CLI/ProjectMenu.php rename to src/ncc/CLI/Management/ProjectMenu.php index 9f8eaef..b7ce09e 100644 --- a/src/ncc/CLI/ProjectMenu.php +++ b/src/ncc/CLI/Management/ProjectMenu.php @@ -1,18 +1,45 @@ getSources(); + + if(count($sources) == 0) + { + Console::out('No remote sources defined.', 1); + return; + } + + Console::out('Remote sources:', 1); + foreach($sources as $source) + { + Console::out(' - ' . $source->Name . ' (' . $source->Host . ')', 1); + } + + Console::out('Total: ' . count($sources), 1); + } + + /** + * @param $args + * @return void + */ + public static function addEntry($args): void + { + if(Resolver::resolveScope() !== Scopes::System) + { + Console::outError('Insufficient permissions to add entry.', true, 1); + return; + } + + $name = $args['name'] ?? null; + $type = $args['type'] ?? null; + $host = $args['host'] ?? null; + $ssl = $args['ssl'] ?? null; + + if($name == null) + { + Console::outError(sprintf('Missing required argument \'%s\'.', 'name'), true, 1); + return; + } + + if($type == null) + { + Console::outError(sprintf('Missing required argument \'%s\'.', 'type'), true, 1); + return; + } + + if($host == null) + { + Console::outError(sprintf('Missing required argument \'%s\'.', 'host'), true, 1); + return; + } + + if($ssl !== null) + { + $ssl = Functions::cbool($ssl); + } + + $source_manager = new RemoteSourcesManager(); + $source = new DefinedRemoteSource(); + $source->Name = $name; + $source->Type = $type; + $source->Host = $host; + $source->SSL = $ssl; + + if(!$source_manager->addRemoteSource($source)) + { + Console::outError(sprintf('Cannot add entry \'%s\', it already exists', $name), true, 1); + return; + } + + try + { + $source_manager->save(); + } + catch (IOException $e) + { + Console::outException('Cannot save remote sources file.', $e, 1); + return; + } + + Console::out(sprintf('Entry \'%s\' added successfully.', $name)); + } + + /** + * Removes an existing entry from the vault. + * + * @param $args + * @return void + */ + private static function removeEntry($args): void + { + $ResolvedScope = Resolver::resolveScope(); + + if($ResolvedScope !== Scopes::System) + Console::outError('Insufficient permissions to remove entries'); + + $name = $args['name'] ?? null; + + if($name == null) + { + Console::outError(sprintf('Missing required argument \'%s\'.', 'name'), true, 1); + return; + } + + $source_manager = new RemoteSourcesManager(); + + if(!$source_manager->deleteRemoteSource($name)) + { + Console::outError(sprintf('Cannot remove entry \'%s\', it does not exist', $name), true, 1); + return; + } + + try + { + $source_manager->save(); + } + catch (IOException $e) + { + Console::outException('Cannot save remote sources file.', $e, 1); + return; + + } + Console::out(sprintf('Entry \'%s\' removed successfully.', $name)); + } + + /** + * Displays the main options section + * + * @return void + */ + private static function displayOptions(): void + { + Console::out('Usage: ncc sources {command} [options]'); + Console::out('Options:'); + Console::outHelpSections([ + new CliHelpSection(['help'], 'Displays this help menu about the sources command'), + new CliHelpSection(['add'], 'Adds a new entry to the list of remote sources (See below)'), + new CliHelpSection(['remove', '--name'], 'Removes an entry from the list'), + new CliHelpSection(['list'], 'Lists all entries defined as remote sources'), + ]); + Console::out((string)null); + + } + } \ No newline at end of file diff --git a/src/ncc/CLI/PhpMenu.php b/src/ncc/CLI/PhpMenu.php deleted file mode 100644 index 1c10f39..0000000 --- a/src/ncc/CLI/PhpMenu.php +++ /dev/null @@ -1,59 +0,0 @@ -toString($options_padding)); - } - } - } \ No newline at end of file diff --git a/src/ncc/Classes/BashExtension/BashRunner.php b/src/ncc/Classes/BashExtension/BashRunner.php new file mode 100644 index 0000000..c10b993 --- /dev/null +++ b/src/ncc/Classes/BashExtension/BashRunner.php @@ -0,0 +1,56 @@ +Execute->Target = null; + $execution_unit->ExecutionPolicy = $policy; + $execution_unit->Data = IO::fread($path); + + return $execution_unit; + } + + /** + * @inheritDoc + */ + public static function getFileExtension(): string + { + return '.bash'; + } + } \ No newline at end of file diff --git a/src/ncc/Classes/ComposerExtension/ComposerSource.php b/src/ncc/Classes/ComposerExtension/ComposerSourceBuiltin.php similarity index 54% rename from src/ncc/Classes/ComposerExtension/ComposerSource.php rename to src/ncc/Classes/ComposerExtension/ComposerSourceBuiltin.php index d732939..a70bb18 100644 --- a/src/ncc/Classes/ComposerExtension/ComposerSource.php +++ b/src/ncc/Classes/ComposerExtension/ComposerSourceBuiltin.php @@ -1,6 +1,26 @@ toStandard())); } + /** + * Works with a local composer.json file and attempts to compile the required packages + * and their dependencies, returns the path to the compiled package. + * + * @param string $path + * @return string + * @throws AccessDeniedException + * @throws BuildConfigurationNotFoundException + * @throws BuildException + * @throws ComposerDisabledException + * @throws ComposerException + * @throws ComposerNotAvailableException + * @throws DirectoryNotFoundException + * @throws FileNotFoundException + * @throws IOException + * @throws InternalComposerNotAvailableException + * @throws MalformedJsonException + * @throws PackageNotFoundException + * @throws PackagePreparationFailedException + * @throws ProjectConfigurationNotFoundException + * @throws UnsupportedCompilerExtensionException + * @throws UserAbortedOperationException + */ + public static function fromLocal(string $path): string + { + // Check if the file composer.json exists + if (!file_exists($path . DIRECTORY_SEPARATOR . 'composer.json')) + throw new FileNotFoundException(sprintf('File "%s" not found', $path . DIRECTORY_SEPARATOR . 'composer.json')); + + // Execute composer with options + $options = self::getOptions(); + $composer_exec = self::getComposerPath(); + $process = new Process([$composer_exec, 'install']); + self::prepareProcess($process, $path, $options); + + Console::outDebug(sprintf('executing %s', $process->getCommandLine())); + $process->run(function ($type, $buffer) { + Console::out($buffer, false); + }); + + if (!$process->isSuccessful()) + throw new ComposerException($process->getErrorOutput()); + + $filesystem = new Filesystem(); + if($filesystem->exists($path . DIRECTORY_SEPARATOR . 'build')) + $filesystem->remove($path . DIRECTORY_SEPARATOR . 'build'); + $filesystem->mkdir($path . DIRECTORY_SEPARATOR . 'build'); + + // Compile dependencies + self::compilePackages($path . DIRECTORY_SEPARATOR . 'composer.lock'); + + $composer_lock = Functions::loadJson(IO::fread($path . DIRECTORY_SEPARATOR . 'composer.lock'), Functions::FORCE_ARRAY); + $version_map = self::getVersionMap(ComposerLock::fromArray($composer_lock)); + + // Finally convert the main package's composer.json to package.json and compile it + ComposerSourceBuiltin::convertProject($path, $version_map); + $project_manager = new ProjectManager($path); + $project_manager->load(); + $built_package = $project_manager->build(); + + RuntimeCache::setFileAsTemporary($built_package); + return $built_package; + } + /** * @param string $composer_lock_path * @return array @@ -109,7 +193,6 @@ * @throws PackagePreparationFailedException * @throws ProjectConfigurationNotFoundException * @throws UnsupportedCompilerExtensionException - * @throws UnsupportedRunnerException */ private static function compilePackages(string $composer_lock_path): array { @@ -129,108 +212,19 @@ } $filesystem->mkdir($base_dir . DIRECTORY_SEPARATOR . 'build'); + $version_map = self::getVersionMap($composer_lock); foreach ($composer_lock->Packages as $package) { $package_path = $base_dir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package->Name; - // Generate the package configuration - $project_configuration = ComposerSource::generateProjectConfiguration($package->Name, $composer_lock); - // Process the source files - if ($package->Autoload !== null) - { - $source_directory = $package_path . DIRECTORY_SEPARATOR . '.src'; - if ($filesystem->exists($source_directory)) - { - $filesystem->remove($source_directory); - } - $filesystem->mkdir($source_directory); - $source_directories = []; - $static_files = []; + // Load the composer lock file + $composer_package = $composer_lock->getPackage($package->Name); + if ($composer_package == null) + throw new PackageNotFoundException(sprintf('Package "%s" not found in composer lock file', $package->Name)); - // TODO: Implement static files handling - - // Extract all the source directories - if ($package->Autoload->Psr4 !== null && count($package->Autoload->Psr4) > 0) - { - Console::outVerbose('Extracting PSR-4 source directories'); - foreach ($package->Autoload->Psr4 as $namespace_pointer) - { - if ($namespace_pointer->Path !== null && !in_array($namespace_pointer->Path, $source_directories)) - { - $source_directories[] = $package_path . DIRECTORY_SEPARATOR . $namespace_pointer->Path; - } - } - } - - if ($package->Autoload->Psr0 !== null && count($package->Autoload->Psr0) > 0) - { - Console::outVerbose('Extracting PSR-0 source directories'); - foreach ($package->Autoload->Psr0 as $namespace_pointer) - { - if ($namespace_pointer->Path !== null && !in_array($namespace_pointer->Path, $source_directories)) - { - $source_directories[] = $package_path . DIRECTORY_SEPARATOR . $namespace_pointer->Path; - } - } - } - - if ($package->Autoload->Files !== null && count($package->Autoload->Files) > 0) - { - Console::outVerbose('Extracting static files'); - foreach ($package->Autoload->Files as $file) - { - $static_files[] = $package_path . DIRECTORY_SEPARATOR . $file; - } - } - - Console::outDebug(sprintf('source directories: %s', implode(', ', $source_directories))); - - // First scan the project files and create a file struct. - $DirectoryScanner = new DirectoryScanner(); - - // TODO: Implement exclude-class handling - try - { - $DirectoryScanner->unsetFlag(FilesystemIterator::FOLLOW_SYMLINKS); - } - catch (Exception $e) - { - throw new PackagePreparationFailedException('Cannot unset flag \'FOLLOW_SYMLINKS\' in DirectoryScanner, ' . $e->getMessage(), $e); - } - - // Include file components that can be compiled - $DirectoryScanner->setIncludes(ComponentFileExtensions::Php); - - foreach ($source_directories as $directory) - { - /** @var SplFileInfo $item */ - /** @noinspection PhpRedundantOptionalArgumentInspection */ - foreach ($DirectoryScanner($directory, True) as $item) - { - if (is_dir($item->getPathName())) - continue; - - $parsed_path = str_ireplace($package_path . DIRECTORY_SEPARATOR, '', $item->getPathName()); - - Console::outDebug(sprintf('copying file %s for package %s', $parsed_path, $package->Name)); - $filesystem->copy($item->getPathName(), $source_directory . DIRECTORY_SEPARATOR . $parsed_path); - } - } - - if (count($static_files) > 0) - { - $project_configuration->Project->Options['static_files'] = $static_files; - $parsed_path = str_ireplace($package_path . DIRECTORY_SEPARATOR, '', $item->getPathName()); - if (!$filesystem->exists($source_directory . DIRECTORY_SEPARATOR . $parsed_path)) - { - Console::outDebug(sprintf('copying file %s for package %s', $parsed_path, $package->Name)); - $filesystem->copy($item->getPathName(), $source_directory . DIRECTORY_SEPARATOR . $parsed_path); - } - } - - $project_configuration->toFile($package_path . DIRECTORY_SEPARATOR . 'project.json'); - } + // Convert it to a NCC project configuration + $project_configuration = self::convertProject($package_path, $version_map, $composer_package); // Load the project $project_manager = new ProjectManager($package_path); @@ -238,15 +232,31 @@ $built_package = $project_manager->build(); // Copy the project to the build directory - $out_path = $base_dir . DIRECTORY_SEPARATOR . 'build' . DIRECTORY_SEPARATOR . sprintf('%s=%s.ncc', $project_configuration->Assembly->Package, $project_configuration->Assembly->Version); + $out_path = $base_dir . DIRECTORY_SEPARATOR . 'build' . DIRECTORY_SEPARATOR . sprintf('%s.ncc', $project_configuration->Assembly->Package); $filesystem->copy($built_package, $out_path); + $filesystem->remove($built_package); $built_packages[$project_configuration->Assembly->Package] = $out_path; - } return $built_packages; } + /** + * Returns array of versions from the ComposerLock file + * + * @param ComposerLock $composerLock + * @return array + */ + private static function getVersionMap(ComposerLock $composerLock): array + { + $version_map = []; + foreach($composerLock->Packages as $package) + { + $version_map[$package->Name] = $package->Version; + } + return $version_map; + } + /** * Converts a composer package name to a valid package name * @@ -271,22 +281,33 @@ return null; } + /** + * Returns a valid version from a version map + * + * @param string $package_name + * @param array $version_map + * @return string + */ + private static function versionMap(string $package_name, array $version_map): string + { + if (array_key_exists($package_name, $version_map)) + { + return Functions::convertToSemVer($version_map[$package_name]); + } + + return '1.0.0'; + } + /** * Generates a project configuration from a package selection * from the composer.lock file * - * @param string $package_name - * @param ComposerLock $composer_lock + * @param ComposerJson $composer_package + * @param array $version_map * @return ProjectConfiguration - * @throws PackageNotFoundException */ - private static function generateProjectConfiguration(string $package_name, ComposerLock $composer_lock): ProjectConfiguration + private static function generateProjectConfiguration(ComposerJson $composer_package, array $version_map): ProjectConfiguration { - // Load the composer lock file - $composer_package = $composer_lock->getPackage($package_name); - if ($composer_package == null) - throw new PackageNotFoundException(sprintf('Package "%s" not found in composer lock file', $package_name)); - // Generate a new project configuration object $project_configuration = new ProjectConfiguration(); @@ -294,37 +315,41 @@ $project_configuration->Assembly->Name = $composer_package->Name; if (isset($composer_package->Description)) $project_configuration->Assembly->Description = $composer_package->Description; - if (isset($composer_package->Version)) - $project_configuration->Assembly->Version = Functions::parseVersion($composer_package->Version); + + if(isset($version_map[$composer_package->Name])) + $project_configuration->Assembly->Version = self::versionMap($composer_package->Name, $version_map); + if($project_configuration->Assembly->Version == null || $project_configuration->Assembly->Version == '') + $project_configuration->Assembly->Version = '1.0.0'; + $project_configuration->Assembly->UUID = Uuid::v1()->toRfc4122(); - $project_configuration->Assembly->Package = self::toPackageName($package_name); + $project_configuration->Assembly->Package = self::toPackageName($composer_package->Name); + + // Add the update source + $project_configuration->Project->UpdateSource = new ProjectConfiguration\UpdateSource(); + $project_configuration->Project->UpdateSource->Source = sprintf('%s@composer', str_ireplace('\\', '/', $composer_package->Name)); + $project_configuration->Project->UpdateSource->Repository = null; // Process the dependencies - foreach ($composer_package->Require as $item) + if($composer_package->Require !== null && count($composer_package->Require) > 0) { - $package_name = self::toPackageName($item->PackageName); - $package_version = $composer_lock->getPackage($item->PackageName)?->Version; - if ($package_version == null) + foreach ($composer_package->Require as $item) { - $package_version = '1.0.0'; + // Check if the dependency is already in the project configuration + $package_name = self::toPackageName($item->PackageName); + if ($package_name == null) + continue; + $dependency = new ProjectConfiguration\Dependency(); + $dependency->Name = $package_name; + $dependency->SourceType = DependencySourceType::Local; + $dependency->Version = self::versionMap($item->PackageName, $version_map); + $dependency->Source = $package_name . '.ncc'; + $project_configuration->Build->addDependency($dependency); } - else - { - $package_version = Functions::parseVersion($package_version); - } - if ($package_name == null) - continue; - $dependency = new ProjectConfiguration\Dependency(); - $dependency->Name = $package_name; - $dependency->SourceType = DependencySourceType::Local; - $dependency->Version = $package_version; - $dependency->Source = $package_name . '=' . $dependency->Version . '.ncc'; - $project_configuration->Build->Dependencies[] = $dependency; } // Create a build configuration - $build_configuration = new ProjectConfiguration\BuildConfiguration(); + $build_configuration = new ProjectConfiguration\Build\BuildConfiguration(); $build_configuration->Name = 'default'; $build_configuration->OutputPath = 'build'; @@ -341,22 +366,6 @@ return $project_configuration; } - /** - * Extracts a version if available from the input - * - * @param string $input - * @return string|null - */ - private static function extractVersion(string $input): ?string - { - if (stripos($input, ':')) - { - return explode(':', $input)[1]; - } - - return null; - } - /** * Gets the applicable options configured for composer * @@ -437,7 +446,6 @@ * @param string $vendor * @param string $package * @param string|null $version - * @param array $options * @return string * @throws AccessDeniedException * @throws ComposerDisabledException @@ -449,13 +457,15 @@ * @throws InvalidScopeException * @throws UserAbortedOperationException */ - private static function require(string $vendor, string $package, ?string $version = null, array $options = []): string + private static function require(string $vendor, string $package, ?string $version = null): string { if (Resolver::resolveScope() !== Scopes::System) throw new AccessDeniedException('Insufficient permissions to require'); if ($version == null) $version = '*'; + if($version == 'latest') + $version = '*'; $tpl_file = __DIR__ . DIRECTORY_SEPARATOR . 'composer.jtpl'; if (!file_exists($tpl_file)) @@ -482,29 +492,11 @@ // Execute composer with options $options = self::getOptions(); $process = new Process(array_merge([$composer_exec, 'require'], $options)); - $process->setWorkingDirectory($tmp_dir); - - // Check if scripts are enabled while running as root - if (!in_array('--no-scripts', $options) && Resolver::resolveScope() == Scopes::System) - { - Console::outWarning('composer scripts are enabled while running as root, this can allow malicious scripts to run as root'); - if (!isset($options['--no-interaction'])) - { - if (!Console::getBooleanInput('Do you want to continue?')) - throw new UserAbortedOperationException('The operation was aborted by the user'); - - // The user understands the risks and wants to continue - $process->setEnv(['COMPOSER_ALLOW_SUPERUSER' => 1]); - } - } - else - { - // Composer is running "safely". We can disable the superuser check - $process->setEnv(['COMPOSER_ALLOW_SUPERUSER' => 1]); - } + self::prepareProcess($process, $tmp_dir, $options); Console::outDebug(sprintf('executing %s', $process->getCommandLine())); - $process->run(function ($type, $buffer) { + $process->run(function ($type, $buffer) + { Console::out($buffer, false); }); @@ -558,4 +550,206 @@ throw new ComposerNotAvailableException('No composer executable path is configured'); } + + /** + * @param Process $process + * @param string $path + * @param array $options + * @return void + * @throws UserAbortedOperationException + */ + private static function prepareProcess(Process $process, string $path, array $options): void + { + $process->setWorkingDirectory($path); + + // Check if scripts are enabled while running as root + if (!in_array('--no-scripts', $options) && Resolver::resolveScope() == Scopes::System) + { + Console::outWarning('composer scripts are enabled while running as root, this can allow malicious scripts to run as root'); + if (!isset($options['--no-interaction'])) + { + if (!Console::getBooleanInput('Do you want to continue?')) + throw new UserAbortedOperationException('The operation was aborted by the user'); + + // The user understands the risks and wants to continue + $process->setEnv(['COMPOSER_ALLOW_SUPERUSER' => 1]); + } + } + else + { + // Composer is running "safely". We can disable the superuser check + $process->setEnv(['COMPOSER_ALLOW_SUPERUSER' => 1]); + } + } + + /** + * Converts a composer project to a NCC project + * + * @param string $package_path + * @param array $version_map + * @param mixed $composer_package + * @return ProjectConfiguration + * @throws AccessDeniedException + * @throws FileNotFoundException + * @throws IOException + * @throws MalformedJsonException + * @throws PackagePreparationFailedException + */ + private static function convertProject(string $package_path, array $version_map, ?ComposerJson $composer_package=null): ProjectConfiguration + { + if($composer_package == null) + $composer_package = ComposerJson::fromArray(Functions::loadJsonFile($package_path . DIRECTORY_SEPARATOR . 'composer.json', Functions::FORCE_ARRAY)); + + $project_configuration = ComposerSourceBuiltin::generateProjectConfiguration($composer_package, $version_map); + $filesystem = new Filesystem(); + + // Process the source files + if ($composer_package->Autoload !== null) + { + $source_directory = $package_path . DIRECTORY_SEPARATOR . '.src'; + if ($filesystem->exists($source_directory)) + $filesystem->remove($source_directory); + $filesystem->mkdir($source_directory); + $source_directories = []; + $static_files = []; + + // Extract all the source directories + if ($composer_package->Autoload->Psr4 !== null && count($composer_package->Autoload->Psr4) > 0) + { + Console::outVerbose('Extracting PSR-4 source directories'); + foreach ($composer_package->Autoload->Psr4 as $namespace_pointer) + { + if ($namespace_pointer->Path !== null && !in_array($namespace_pointer->Path, $source_directories)) + { + $source_directories[] = $package_path . DIRECTORY_SEPARATOR . $namespace_pointer->Path; + } + } + } + + if ($composer_package->Autoload->Psr0 !== null && count($composer_package->Autoload->Psr0) > 0) + { + Console::outVerbose('Extracting PSR-0 source directories'); + foreach ($composer_package->Autoload->Psr0 as $namespace_pointer) + { + if ($namespace_pointer->Path !== null && !in_array($namespace_pointer->Path, $source_directories)) + { + $source_directories[] = $package_path . DIRECTORY_SEPARATOR . $namespace_pointer->Path; + } + } + } + + if ($composer_package->Autoload->Files !== null && count($composer_package->Autoload->Files) > 0) + { + Console::outVerbose('Extracting static files'); + foreach ($composer_package->Autoload->Files as $file) + { + $static_files[] = $package_path . DIRECTORY_SEPARATOR . $file; + } + } + + Console::outDebug(sprintf('source directories: %s', implode(', ', $source_directories))); + + // First scan the project files and create a file struct. + $DirectoryScanner = new DirectoryScanner(); + + // TODO: Implement exclude-class handling + try + { + $DirectoryScanner->unsetFlag(FilesystemIterator::FOLLOW_SYMLINKS); + } + catch (Exception $e) + { + throw new PackagePreparationFailedException('Cannot unset flag \'FOLLOW_SYMLINKS\' in DirectoryScanner, ' . $e->getMessage(), $e); + } + + // Include file components that can be compiled + $DirectoryScanner->setIncludes(ComponentFileExtensions::Php); + + foreach ($source_directories as $directory) + { + /** @var SplFileInfo $item */ + /** @noinspection PhpRedundantOptionalArgumentInspection */ + foreach ($DirectoryScanner($directory, True) as $item) + { + if (is_dir($item->getPathName())) + continue; + + $parsed_path = str_ireplace($package_path . DIRECTORY_SEPARATOR, '', $item->getPathName()); + + Console::outDebug(sprintf('copying file %s for package %s', $parsed_path, $composer_package->Name)); + $filesystem->copy($item->getPathName(), $source_directory . DIRECTORY_SEPARATOR . $parsed_path); + } + } + + if (count($static_files) > 0) + { + $project_configuration->Project->Options['static_files'] = $static_files; + + foreach ($static_files as $file) + { + $parsed_path = str_ireplace($package_path . DIRECTORY_SEPARATOR, '', $file); + Console::outDebug(sprintf('copying file %s for package %s', $parsed_path, $composer_package->Name)); + $filesystem->copy($file, $source_directory . DIRECTORY_SEPARATOR . $parsed_path); + } + unset($file); + } + } + + $project_configuration->toFile($package_path . DIRECTORY_SEPARATOR . 'project.json'); + + // This part simply displays the package information to the command-line interface + if(ncc::cliMode()) + { + $license_files = [ + 'LICENSE', + 'license', + 'LICENSE.txt', + 'license.txt' + ]; + + foreach($license_files as $license_file) + { + if($filesystem->exists($package_path . DIRECTORY_SEPARATOR . $license_file)) + { + // Check configuration if composer.extension.display_licenses is set + if(Functions::cbool(Functions::getConfigurationProperty('composer.extension.display_licenses'))) + { + Console::out(sprintf('License for package %s:', $composer_package->Name)); + Console::out(IO::fread($package_path . DIRECTORY_SEPARATOR . $license_file)); + break; + } + } + } + + if(Functions::cbool(Functions::getConfigurationProperty('composer.extension.display_authors'))) + { + if($composer_package->Authors !== null && count($composer_package->Authors) > 0) + { + Console::out(sprintf('Authors for package %s:', $composer_package->Name)); + foreach($composer_package->Authors as $author) + { + Console::out(sprintf(' - %s', $author->Name)); + + if($author->Email !== null) + { + Console::out(sprintf(' %s', $author->Email)); + } + + if($author->Homepage !== null) + { + Console::out(sprintf(' %s', $author->Homepage)); + } + + if($author->Role !== null) + { + Console::out(sprintf(' %s', $author->Role)); + } + + } + } + } + } + + return $project_configuration; + } } \ No newline at end of file diff --git a/src/ncc/Classes/EnvironmentConfiguration.php b/src/ncc/Classes/EnvironmentConfiguration.php deleted file mode 100644 index 5910bd0..0000000 --- a/src/ncc/Classes/EnvironmentConfiguration.php +++ /dev/null @@ -1,71 +0,0 @@ - $config) - { - $results[$name] = PhpConfiguration::fromArray($config); - } - - return $results; - } - - /** - * Returns an array of only the changed configuration values - * - * @return PhpConfiguration[] - */ - public static function getChangedValues(): array - { - $results = []; - - foreach(ini_get_all() as $name => $config) - { - $config = PhpConfiguration::fromArray($config); - if($config->LocalValue !== $config->GlobalValue) - { - $results[$name] = $config; - } - } - - return $results; - } - - /** - * @param string $file_path - * @return void - */ - public static function export(string $file_path) - { - $configuration = []; - foreach(self::getChangedValues() as $changedValue) - { - $configuration[$changedValue->getName()] = $changedValue->getValue(); - } - - // TODO: Implement ini writing process here - } - - public static function import(string $file_path) - { - // TODO: Implement ini reading process here - $configuration = []; - foreach($configuration as $item => $value) - { - ini_set($item, $value); - } - } - } \ No newline at end of file diff --git a/src/ncc/Classes/GitClient.php b/src/ncc/Classes/GitClient.php new file mode 100644 index 0000000..6245ed7 --- /dev/null +++ b/src/ncc/Classes/GitClient.php @@ -0,0 +1,132 @@ +setTimeout(3600); // 1 hour + $process->run(function ($type, $buffer) + { + Console::outVerbose($buffer); + }); + + if (!$process->isSuccessful()) + throw new GitCloneException($process->getErrorOutput()); + + Console::outVerbose('Repository cloned to: ' . $path); + + return $path; + } + + /** + * Checks out a specific branch or tag. + * + * @param string $path + * @param string $branch + * @throws GitCheckoutException + */ + public static function checkout(string $path, string $branch): void + { + Console::outVerbose('Checking out branch' . $branch); + $process = new Process(["git", "checkout", $branch], $path); + $process->setTimeout(3600); // 1 hour + $process->run(function ($type, $buffer) + { + if (Process::ERR === $type) + { + Console::outWarning($buffer); + } + else + { + Console::outVerbose($buffer); + } + }); + + if (!$process->isSuccessful()) + throw new GitCheckoutException($process->getErrorOutput()); + + Console::outVerbose('Checked out branch: ' . $branch); + } + + /** + * Returns an array of tags that are available in the repository. + * + * @param string $path + * @return array + * @throws GitTagsException + */ + public static function getTags(string $path): array + { + Console::outVerbose('Getting tags for repository: ' . $path); + $process = new Process(["git", "fetch", '--all', '--tags'] , $path); + $process->setTimeout(3600); // 1 hour + $process->run(function ($type, $buffer) + { + Console::outVerbose($buffer); + }); + + if (!$process->isSuccessful()) + throw new GitTagsException($process->getErrorOutput()); + + $process = new Process(['git', '--no-pager', 'tag', '-l'] , $path); + + $process->run(function ($type, $buffer) + { + Console::outVerbose($buffer); + }); + + if (!$process->isSuccessful()) + throw new GitTagsException($process->getErrorOutput()); + + $tags = explode(PHP_EOL, $process->getOutput()); + $tags = array_filter($tags, function ($tag) + { + return !empty($tag); + }); + + Console::outDebug('found ' . count($tags) . ' tags'); + return array_filter($tags); + } + + } \ No newline at end of file diff --git a/src/ncc/Classes/GithubExtension/GithubService.php b/src/ncc/Classes/GithubExtension/GithubService.php new file mode 100644 index 0000000..4b8bcc2 --- /dev/null +++ b/src/ncc/Classes/GithubExtension/GithubService.php @@ -0,0 +1,277 @@ +SSL ? "https" : "http"); + $owner_f = str_ireplace("/", "%2F", $packageInput->Vendor); + $owner_f = str_ireplace(".", "%2F", $owner_f); + $repository = urlencode($packageInput->Package); + $httpRequest->Url = $protocol . '://' . $definedRemoteSource->Host . "/repos/$owner_f/$repository"; + $response_decoded = self::getJsonResponse($httpRequest, $entry); + + $query = new RepositoryQueryResults(); + $query->Files->GitSshUrl = ($response_decoded['ssh_url'] ?? null); + $query->Files->GitHttpUrl = ($response_decoded['clone_url'] ?? null); + $query->Version = Functions::convertToSemVer($response_decoded['default_branch'] ?? null); + $query->ReleaseDescription = ($response_decoded['description'] ?? null); + $query->ReleaseName = ($response_decoded['name'] ?? null); + + + return $query; + } + + /** + * Returns the download URL of the requested version of the package. + * + * @param RemotePackageInput $packageInput + * @param DefinedRemoteSource $definedRemoteSource + * @param Entry|null $entry + * @return RepositoryQueryResults + * @throws AuthenticationException + * @throws GithubServiceException + * @throws GitlabServiceException + * @throws HttpException + * @throws MalformedJsonException + * @throws VersionNotFoundException + */ + public static function getRelease(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults + { + return self::processReleases($packageInput, $definedRemoteSource, $entry); + } + + /** + * @param RemotePackageInput $packageInput + * @param DefinedRemoteSource $definedRemoteSource + * @param Entry|null $entry + * @return RepositoryQueryResults + * @throws AuthenticationException + * @throws GithubServiceException + * @throws GitlabServiceException + * @throws HttpException + * @throws MalformedJsonException + * @throws VersionNotFoundException + */ + public static function getNccPackage(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults + { + return self::processReleases($packageInput, $definedRemoteSource, $entry); + } + + /** + * Returns a list of all releases of the given repository with their download URL. + * + * @param RemotePackageInput $packageInput + * @param DefinedRemoteSource $definedRemoteSource + * @param Entry|null $entry + * @return array + * @throws AuthenticationException + * @throws GithubServiceException + * @throws GitlabServiceException + * @throws HttpException + * @throws MalformedJsonException + */ + private static function getReleases(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): array + { + $httpRequest = new HttpRequest(); + $protocol = ($definedRemoteSource->SSL ? "https" : "http"); + $owner_f = str_ireplace("/", "%2F", $packageInput->Vendor); + $owner_f = str_ireplace(".", "%2F", $owner_f); + $repository = urlencode($packageInput->Package); + $httpRequest->Url = $protocol . '://' . $definedRemoteSource->Host . "/repos/$owner_f/$repository/releases"; + $response_decoded = self::getJsonResponse($httpRequest, $entry); + + if(count($response_decoded) == 0) + return []; + + $return = []; + foreach($response_decoded as $release) + { + $query_results = new RepositoryQueryResults(); + $query_results->Version = Functions::convertToSemVer($release['tag_name']); + $query_results->ReleaseName = $release['name']; + $query_results->ReleaseDescription = $release['body']; + $query_results->Files->ZipballUrl = ($release['zipball_url'] ?? null); + $query_results->Files->TarballUrl = ($release['tarball_url'] ?? null); + + if(isset($release['assets'])) + { + foreach($release['assets'] as $asset) + { + $parsed_asset = self::parseAsset($asset); + if($parsed_asset !== null) + $query_results->Files->PackageUrl = $parsed_asset; + } + } + + $return[$query_results->Version] = $query_results; + } + + return $return; + } + + /** + * Returns the asset download URL if it points to a .ncc package. + * + * @param array $asset + * @return string|null' + */ + private static function parseAsset(array $asset): ?string + { + if(isset($asset['browser_download_url'])) + { + $file_extension = pathinfo($asset['browser_download_url'], PATHINFO_EXTENSION); + if($file_extension == 'ncc') + return $asset['browser_download_url']; + } + + return null; + } + + /** + * @param HttpRequest $httpRequest + * @param Entry|null $entry + * @return array + * @throws AuthenticationException + * @throws GithubServiceException + * @throws GitlabServiceException + * @throws HttpException + * @throws MalformedJsonException + */ + private static function getJsonResponse(HttpRequest $httpRequest, ?Entry $entry): array + { + $httpRequest->Type = HttpRequestType::GET; + $httpRequest = Functions::prepareGitServiceRequest($httpRequest, $entry, false); + $httpRequest->Headers[] = 'X-GitHub-Api-Version: 2022-11-28'; + $httpRequest->Headers[] = 'Accept: application/vnd.github+json'; + + $response = HttpClient::request($httpRequest, true); + + if ($response->StatusCode != 200) + throw new GithubServiceException(sprintf('Failed to fetch releases for the given repository. Status code: %s', $response->StatusCode)); + + return Functions::loadJson($response->Body, Functions::FORCE_ARRAY); + } + + /** + * @param RemotePackageInput $packageInput + * @param DefinedRemoteSource $definedRemoteSource + * @param Entry|null $entry + * @return mixed + * @throws AuthenticationException + * @throws GithubServiceException + * @throws GitlabServiceException + * @throws HttpException + * @throws MalformedJsonException + * @throws VersionNotFoundException + */ + private static function processReleases(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry): mixed + { + $releases = self::getReleases($packageInput, $definedRemoteSource, $entry); + + if (count($releases) === 0) + throw new VersionNotFoundException('No releases found for the given repository.'); + + if ($packageInput->Version == Versions::Latest) + { + $latest_version = null; + foreach ($releases as $release) + { + if ($latest_version == null) + { + $latest_version = $release->Version; + continue; + } + + if (VersionComparator::compareVersion($release->Version, $latest_version) == 1) + $latest_version = $release->Version; + } + + return $releases[$latest_version]; + } + + // Query a specific version + if (!isset($releases[$packageInput->Version])) + { + // Find the closest thing to the requested version + $selected_version = null; + foreach ($releases as $version => $url) + { + if ($selected_version == null) + { + $selected_version = $version; + continue; + } + + if (VersionComparator::compareVersion($version, $packageInput->Version) == 1) + $selected_version = $version; + } + + if ($selected_version == null) + throw new VersionNotFoundException('No releases found for the given repository.'); + } + else + { + $selected_version = $packageInput->Version; + } + + if (!isset($releases[$selected_version])) + throw new VersionNotFoundException(sprintf('No releases found for the given repository. (Selected version: %s)', $selected_version)); + + return $releases[$selected_version]; + } + } \ No newline at end of file diff --git a/src/ncc/Classes/GitlabExtension/GitlabService.php b/src/ncc/Classes/GitlabExtension/GitlabService.php new file mode 100644 index 0000000..3a4dd0e --- /dev/null +++ b/src/ncc/Classes/GitlabExtension/GitlabService.php @@ -0,0 +1,242 @@ +SSL ? "https" : "http"); + $owner_f = str_ireplace("/", "%2F", $packageInput->Vendor); + $owner_f = str_ireplace(".", "%2F", $owner_f); + $project_f = str_ireplace("/", "%2F", $packageInput->Package); + $project_f = str_ireplace(".", "%2F", $project_f); + $httpRequest->Url = $protocol . '://' . $definedRemoteSource->Host . "/api/v4/projects/$owner_f%2F$project_f"; + $httpRequest = Functions::prepareGitServiceRequest($httpRequest, $entry); + + $response = HttpClient::request($httpRequest, true); + + if($response->StatusCode != 200) + throw new GitlabServiceException(sprintf('Failed to fetch releases for the given repository. Status code: %s', $response->StatusCode)); + + $response_decoded = Functions::loadJson($response->Body, Functions::FORCE_ARRAY); + + $query = new RepositoryQueryResults(); + $query->Files->GitSshUrl = ($response_decoded['ssh_url_to_repo'] ?? null); + $query->Files->GitHttpUrl = ($response_decoded['http_url_to_repo'] ?? null); + $query->Version = Functions::convertToSemVer($response_decoded['default_branch']) ?? null; + $query->ReleaseDescription = ($response_decoded['description'] ?? null); + $query->ReleaseName = ($response_decoded['name'] ?? null); + + return $query; + } + + /** + * Returns the download URL of the requested version of the package. + * + * @param RemotePackageInput $packageInput + * @param DefinedRemoteSource $definedRemoteSource + * @param Entry|null $entry + * @return RepositoryQueryResults + * @throws AuthenticationException + * @throws GitlabServiceException + * @throws HttpException + * @throws MalformedJsonException + * @throws VersionNotFoundException + */ + public static function getRelease(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults + { + $releases = self::getReleases($packageInput->Vendor, $packageInput->Package, $definedRemoteSource, $entry); + + if(count($releases) === 0) + throw new VersionNotFoundException('No releases found for the given repository.'); + + // Query the latest package only + if($packageInput->Version == Versions::Latest) + { + $latest_version = null; + foreach($releases as $release) + { + if($latest_version == null) + { + $latest_version = $release->Version; + continue; + } + + if(VersionComparator::compareVersion($release->Version, $latest_version) == 1) + $latest_version = $release->Version; + } + + return $releases[$latest_version]; + } + + // Query a specific version + if(!isset($releases[$packageInput->Version])) + { + // Find the closest thing to the requested version + $selected_version = null; + foreach($releases as $version => $url) + { + if($selected_version == null) + { + $selected_version = $version; + continue; + } + + if(VersionComparator::compareVersion($version, $packageInput->Version) == 1) + $selected_version = $version; + } + + if($selected_version == null) + throw new VersionNotFoundException('No releases found for the given repository.'); + } + else + { + $selected_version = $packageInput->Version; + } + + if(!isset($releases[$selected_version])) + throw new VersionNotFoundException(sprintf('No releases found for the given repository. (Selected version: %s)', $selected_version)); + + return $releases[$selected_version]; + } + + /** + * @param RemotePackageInput $packageInput + * @param DefinedRemoteSource $definedRemoteSource + * @param Entry|null $entry + * @return RepositoryQueryResults + * @throws NotSupportedException + */ + public static function getNccPackage(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults + { + throw new NotSupportedException(sprintf('The given repository source "%s" does not support ncc packages.', $definedRemoteSource->Host)); + } + + /** + * Returns an array of all the tags for the given owner and repository name. + * + * @param string $owner + * @param string $repository + * @param DefinedRemoteSource $definedRemoteSource + * @param Entry|null $entry + * @return array + * @throws AuthenticationException + * @throws GitlabServiceException + * @throws HttpException + * @throws MalformedJsonException + */ + private static function getReleases(string $owner, string $repository, DefinedRemoteSource $definedRemoteSource, ?Entry $entry): array + { + $httpRequest = new HttpRequest(); + $protocol = ($definedRemoteSource->SSL ? "https" : "http"); + $owner_f = str_ireplace("/", "%2F", $owner); + $owner_f = str_ireplace(".", "%2F", $owner_f); + $repository_f = str_ireplace("/", "%2F", $repository); + $repository_f = str_ireplace(".", "%2F", $repository_f); + + $httpRequest->Url = $protocol . '://' . $definedRemoteSource->Host . "/api/v4/projects/$owner_f%2F$repository_f/releases"; + $httpRequest = Functions::prepareGitServiceRequest($httpRequest, $entry); + + $response = HttpClient::request($httpRequest, true); + + if($response->StatusCode != 200) + throw new GitlabServiceException(sprintf('Failed to fetch releases for the given repository. Status code: %s', $response->StatusCode)); + + $response_decoded = Functions::loadJson($response->Body, Functions::FORCE_ARRAY); + + if(count($response_decoded) == 0) + return []; + + $return = []; + foreach($response_decoded as $release) + { + $query_results = new RepositoryQueryResults(); + $query_results->ReleaseName = ($release['name'] ?? null); + $query_results->ReleaseDescription = ($release['description'] ?? null); + $query_results->Version = Functions::convertToSemVer($release['tag_name']); + + if(isset($release['assets']) && isset($release['assets']['sources'])) + { + if(count($release['assets']['sources']) > 0) + { + foreach($release['assets']['sources'] as $source) + { + if($source['format'] == 'zip') + { + $query_results->Files->ZipballUrl = $source['url']; + break; + } + + if($source['format'] == 'tar.gz') + { + $query_results->Files->ZipballUrl = $source['url']; + break; + } + + if($source['format'] == 'ncc') + { + $query_results->Files->PackageUrl = $source['url']; + break; + } + } + } + } + + $return[$query_results->Version] = $query_results; + } + + return $return; + } + } \ No newline at end of file diff --git a/src/ncc/Classes/HttpClient.php b/src/ncc/Classes/HttpClient.php new file mode 100644 index 0000000..11b47e9 --- /dev/null +++ b/src/ncc/Classes/HttpClient.php @@ -0,0 +1,241 @@ +Url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_HEADER, true); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($curl, CURLOPT_MAXREDIRS, 5); + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($curl, CURLOPT_TIMEOUT, 10); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($curl, CURLOPT_HTTPHEADER, $request->Headers); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $request->Type); + + switch($request->Type) + { + case HttpRequestType::GET: + curl_setopt($curl, CURLOPT_HTTPGET, true); + break; + + case HttpRequestType::POST: + curl_setopt($curl, CURLOPT_POST, true); + if($request->Body !== null) + curl_setopt($curl, CURLOPT_POSTFIELDS, $request->Body); + break; + + case HttpRequestType::PUT: + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT'); + if($request->Body !== null) + curl_setopt($curl, CURLOPT_POSTFIELDS, $request->Body); + break; + + case HttpRequestType::DELETE: + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); + break; + } + + if (is_array($request->Authentication)) + { + curl_setopt($curl, CURLOPT_USERPWD, $request->Authentication[0] . ':' . $request->Authentication[1]); + } + else if (is_string($request->Authentication)) + { + curl_setopt($curl, CURLOPT_USERPWD, $request->Authentication); + } + + foreach ($request->Options as $option => $value) + curl_setopt($curl, $option, $value); + + return $curl; + } + + /** + * Creates a new HTTP request and returns the response. + * + * @param HttpRequest $httpRequest + * @param bool $cache + * @return HttpResponse + * @throws HttpException + */ + public static function request(HttpRequest $httpRequest, bool $cache=false): HttpResponse + { + if($cache) + { + /** @var HttpResponseCache $cache */ + $cache = RuntimeCache::get(sprintf('http_cache_%s', $httpRequest->requestHash())); + if($cache !== null && $cache->getTtl() > time()) + { + Console::outDebug(sprintf('using cached response for %s', $httpRequest->requestHash())); + return $cache->getHttpResponse(); + } + } + + $curl = self::prepareCurl($httpRequest); + + Console::outDebug(sprintf(' => %s request %s', $httpRequest->Type, $httpRequest->Url)); + if($httpRequest->Headers !== null && count($httpRequest->Headers) > 0) + Console::outDebug(sprintf(' => headers: %s', implode(', ', $httpRequest->Headers))); + if($httpRequest->Body !== null) + Console::outDebug(sprintf(' => body: %s', $httpRequest->Body)); + + $response = curl_exec($curl); + + if ($response === false) + { + $error = curl_error($curl); + curl_close($curl); + throw new HttpException($error); + } + + $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE); + $headers = substr($response, 0, $headerSize); + $body = substr($response, $headerSize); + + $httpResponse = new HttpResponse(); + $httpResponse->StatusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $httpResponse->Headers = self::parseHeaders($headers); + $httpResponse->Body = $body; + + Console::outDebug(sprintf(' <= %s response', $httpResponse->StatusCode));/** @noinspection PhpConditionAlreadyCheckedInspection */ + if($httpResponse->Headers !== null && count($httpResponse->Headers) > 0) + Console::outDebug(sprintf(' <= headers: %s', implode(', ', $httpResponse->Headers))); + /** @noinspection PhpConditionAlreadyCheckedInspection */ + if($httpResponse->Body !== null) + Console::outDebug(sprintf(' <= body: %s', $httpResponse->Body)); + + curl_close($curl); + + if($cache) + { + $httpCacheObject = new HttpResponseCache($httpResponse, time() + 60); + RuntimeCache::set(sprintf('http_cache_%s', $httpRequest->requestHash()), $httpCacheObject); + + Console::outDebug(sprintf('cached response for %s', $httpRequest->requestHash())); + } + + return $httpResponse; + } + + /** + * Downloads a file from the given url and saves it to the given path. + * + * @param HttpRequest $httpRequest + * @param string $path + * @return void + * @throws HttpException + */ + public static function download(HttpRequest $httpRequest, string $path): void + { + $curl = self::prepareCurl($httpRequest); + + $fp = fopen($path, 'w'); + curl_setopt($curl, CURLOPT_FILE, $fp); + curl_setopt($curl, CURLOPT_HEADER, 0); + $response = curl_exec($curl); + + if ($response === false) + { + $error = curl_error($curl); + curl_close($curl); + throw new HttpException($error); + } + + curl_close($curl); + fclose($fp); + } + + /** + * Displays the download progress in the console + * + * @param $downloadSize + * @param $downloaded + * @return void + */ + public static function displayProgress($downloadSize, $downloaded): void + { + if(Main::getLogLevel() !== null) + { + switch(Main::getLogLevel()) + { + case LogLevel::Verbose: + case LogLevel::Debug: + case LogLevel::Silent: + Console::outVerbose(sprintf(' <= %s of %s bytes downloaded', $downloaded, $downloadSize)); + break; + + default: + if ($downloadSize > 0) + Console::inlineProgressBar($downloaded, $downloadSize); + break; + } + } + + + } + + /** + * Takes the return headers of a cURL request and parses them into an array. + * + * @param string $input + * @return array + */ + private static function parseHeaders(string $input): array + { + $headers = array(); + $lines = explode("\n", $input); + $headers['HTTP'] = array_shift($lines); + + foreach ($lines as $line) { + $header = explode(':', $line, 2); + if (count($header) == 2) { + $headers[trim($header[0])] = trim($header[1]); + } + } + + return $headers; + } + } \ No newline at end of file diff --git a/src/ncc/Classes/LuaExtension/LuaRunner.php b/src/ncc/Classes/LuaExtension/LuaRunner.php new file mode 100644 index 0000000..599c5b2 --- /dev/null +++ b/src/ncc/Classes/LuaExtension/LuaRunner.php @@ -0,0 +1,53 @@ +Execute->Target = null; + $execution_unit->ExecutionPolicy = $policy; + $execution_unit->Data = IO::fread($path); + + return $execution_unit; + } + + /** + * @inheritDoc + */ + public static function getFileExtension(): string + { + return '.lua'; + } + } \ No newline at end of file diff --git a/src/ncc/Classes/NccExtension/ConstantCompiler.php b/src/ncc/Classes/NccExtension/ConstantCompiler.php index 5ab4597..5540632 100644 --- a/src/ncc/Classes/NccExtension/ConstantCompiler.php +++ b/src/ncc/Classes/NccExtension/ConstantCompiler.php @@ -1,14 +1,33 @@ ProjectType == ProjectType::Composer) + { + $project_path = ComposerSourceBuiltin::fromLocal($project_type->ProjectPath); + } + elseif($project_type->ProjectType == ProjectType::Ncc) + { + $project_manager = new ProjectManager($project_type->ProjectPath); + $project_manager->getProjectConfiguration()->Assembly->Version = $version; + $project_path = $project_manager->build(); + } + else + { + throw new UnsupportedProjectTypeException('The project type \'' . $project_type->ProjectType . '\' is not supported'); + } + + if($version !== null) + { + $package = Package::load($project_path); + $package->Assembly->Version = Functions::convertToSemVer($version); + $package->save($project_path); + } + + return $project_path; + } + catch(Exception $e) + { + throw new BuildException('Failed to build project', $e); + } + } + + /** * Compiles the execution policies of the package * @@ -93,7 +161,7 @@ * @throws AccessDeniedException * @throws FileNotFoundException * @throws IOException - * @throws UnsupportedRunnerException + * @throws RunnerExecutionException */ public static function compileExecutionPolicies(string $path, ProjectConfiguration $configuration): array { @@ -108,6 +176,8 @@ /** @var ProjectConfiguration\ExecutionPolicy $policy */ foreach($configuration->ExecutionPolicies as $policy) { + Console::outVerbose(sprintf('Compiling Execution Policy %s', $policy->Name)); + if($total_items > 5) { Console::inlineProgressBar($processed_items, $total_items); @@ -132,29 +202,29 @@ * @param string $build_configuration * @return string * @throws BuildConfigurationNotFoundException - * @throws BuildException * @throws IOException */ public static function writePackage(string $path, Package $package, ProjectConfiguration $configuration, string $build_configuration=BuildConfigurationValues::DefaultConfiguration): string { + Console::outVerbose(sprintf('Writing package to %s', $path)); + // Write the package to disk $FileSystem = new Filesystem(); $BuildConfiguration = $configuration->Build->getBuildConfiguration($build_configuration); - if($FileSystem->exists($path . $BuildConfiguration->OutputPath)) + if(!$FileSystem->exists($path . $BuildConfiguration->OutputPath)) { - try - { - $FileSystem->remove($path . $BuildConfiguration->OutputPath); - } - catch(\ncc\ThirdParty\Symfony\Filesystem\Exception\IOException $e) - { - throw new BuildException('Cannot delete directory \'' . $path . $BuildConfiguration->OutputPath . '\', ' . $e->getMessage(), $e); - } + Console::outDebug(sprintf('creating output directory %s', $path . $BuildConfiguration->OutputPath)); + $FileSystem->mkdir($path . $BuildConfiguration->OutputPath); } // Finally write the package to the disk $FileSystem->mkdir($path . $BuildConfiguration->OutputPath); $output_file = $path . $BuildConfiguration->OutputPath . DIRECTORY_SEPARATOR . $package->Assembly->Package . '.ncc'; + if($FileSystem->exists($output_file)) + { + Console::outDebug(sprintf('removing existing package %s', $output_file)); + $FileSystem->remove($output_file); + } $FileSystem->touch($output_file); try @@ -169,35 +239,13 @@ return $output_file; } - /** - * Compiles the special formatted constants - * - * @param Package $package - * @param int $timestamp - * @return array - */ - public static function compileRuntimeConstants(Package $package, int $timestamp): array - { - $compiled_constants = []; - - foreach($package->Header->RuntimeConstants as $name => $value) - { - $compiled_constants[$name] = self::compileConstants($value, [ - ConstantReferences::Assembly => $package->Assembly, - ConstantReferences::DateTime => $timestamp, - ConstantReferences::Build => null - ]); - } - - return $compiled_constants; - } - /** * Compiles the constants in the package object * * @param Package $package * @param array $refs * @return void + * @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */ public static function compilePackageConstants(Package &$package, array $refs): void { @@ -206,6 +254,7 @@ $assembly = []; foreach($package->Assembly->toArray() as $key => $value) { + Console::outDebug(sprintf('compiling consts Assembly.%s (%s)', $key, implode(', ', array_keys($refs)))); $assembly[$key] = self::compileConstants($value, $refs); } $package->Assembly = Assembly::fromArray($assembly); @@ -217,11 +266,44 @@ $units = []; foreach($package->ExecutionUnits as $executionUnit) { + Console::outDebug(sprintf('compiling execution unit consts %s (%s)', $executionUnit->ExecutionPolicy->Name, implode(', ', array_keys($refs)))); $units[] = self::compileExecutionUnitConstants($executionUnit, $refs); } $package->ExecutionUnits = $units; unset($units); } + + $compiled_constants = []; + foreach($package->Header->RuntimeConstants as $name => $value) + { + Console::outDebug(sprintf('compiling runtime const %s (%s)', $name, implode(', ', array_keys($refs)))); + $compiled_constants[$name] = self::compileConstants($value, $refs); + } + + $options = []; + foreach($package->Header->Options as $name => $value) + { + if(is_array($value)) + { + $options[$name] = []; + foreach($value as $key => $val) + { + if(!is_string($val)) + continue; + + Console::outDebug(sprintf('compiling option %s.%s (%s)', $name, $key, implode(', ', array_keys($refs)))); + $options[$name][$key] = self::compileConstants($val, $refs); + } + } + else + { + Console::outDebug(sprintf('compiling option %s (%s)', $name, implode(', ', array_keys($refs)))); + $options[$name] = self::compileConstants((string)$value, $refs); + } + } + + $package->Header->Options = $options; + $package->Header->RuntimeConstants = $compiled_constants; } /** @@ -303,6 +385,9 @@ if(isset($refs[ConstantReferences::Install])) $value = ConstantCompiler::compileInstallConstants($value, $refs[ConstantReferences::Install]); + if(isset($refs[ConstantReferences::Runtime])) + $value = ConstantCompiler::compileRuntimeConstants($value); + return $value; } } \ No newline at end of file diff --git a/src/ncc/Classes/NccExtension/Runner.php b/src/ncc/Classes/NccExtension/Runner.php index 2133137..5a71e81 100644 --- a/src/ncc/Classes/NccExtension/Runner.php +++ b/src/ncc/Classes/NccExtension/Runner.php @@ -1,20 +1,35 @@ addUnit($package, $version, $unit, true); $ExecutionPointerManager->executeUnit($package, $version, $unit->ExecutionPolicy->Name); - $ExecutionPointerManager->cleanTemporaryUnits();; + $ExecutionPointerManager->cleanTemporaryUnits(); } } \ No newline at end of file diff --git a/src/ncc/Classes/PerlExtension/PerlRunner.php b/src/ncc/Classes/PerlExtension/PerlRunner.php new file mode 100644 index 0000000..46ee10d --- /dev/null +++ b/src/ncc/Classes/PerlExtension/PerlRunner.php @@ -0,0 +1,56 @@ +Execute->Target = null; + if(!file_exists($path) && !is_file($path)) + throw new FileNotFoundException($path); + $execution_unit->ExecutionPolicy = $policy; + $execution_unit->Data = IO::fread($path); + + return $execution_unit; + } + + /** + * @inheritDoc + */ + public static function getFileExtension(): string + { + return '.pl'; + } + } \ No newline at end of file diff --git a/src/ncc/Classes/PhpExtension/PhpCompiler.php b/src/ncc/Classes/PhpExtension/PhpCompiler.php index bb29ce9..bc5142b 100644 --- a/src/ncc/Classes/PhpExtension/PhpCompiler.php +++ b/src/ncc/Classes/PhpExtension/PhpCompiler.php @@ -1,4 +1,24 @@ package->Dependencies = $this->project->Build->Dependencies; $this->package->MainExecutionPolicy = $this->project->Build->Main; + // Add the option to create a symbolic link to the package + if(isset($this->project->Project->Options['create_symlink']) && $this->project->Project->Options['create_symlink'] === True) + $this->package->Header->Options['create_symlink'] = true; + // Add both the defined constants from the build configuration and the global constants. // Global constants are overridden $this->package->Header->RuntimeConstants = []; $this->package->Header->RuntimeConstants = array_merge( - ($selected_build_configuration?->DefineConstants ?? []), + ($selected_build_configuration->DefineConstants ?? []), ($this->project->Build->DefineConstants ?? []), ($this->package->Header->RuntimeConstants ?? []) ); @@ -105,6 +128,11 @@ $this->package->Header->CompilerVersion = NCC_VERSION_NUMBER; $this->package->Header->Options = $this->project->Project->Options; + if($this->project->Project->UpdateSource !== null) + { + $this->package->Header->UpdateSource = $this->project->Project->UpdateSource; + } + Console::outDebug('scanning project files'); Console::outDebug('theseer\DirectoryScanner - Copyright (c) 2009-2014 Arne Blankerts All rights reserved.'); @@ -128,67 +156,75 @@ // TODO: Re-implement the scanning process outside the compiler, as this is will be redundant // Scan for components first. - Console::outVerbose('Scanning for components... '); - /** @var SplFileInfo $item */ - /** @noinspection PhpRedundantOptionalArgumentInspection */ - foreach($DirectoryScanner($source_path, True) as $item) + + if(file_exists($source_path)) { - // Ignore directories, they're not important. :-) - if(is_dir($item->getPathName())) - continue; + Console::outVerbose('Scanning for components... '); + /** @var SplFileInfo $item */ + /** @noinspection PhpRedundantOptionalArgumentInspection */ + foreach($DirectoryScanner($source_path, True) as $item) + { + // Ignore directories, they're not important. :-) + if(is_dir($item->getPathName())) + continue; - $Component = new Package\Component(); - $Component->Name = Functions::removeBasename($item->getPathname(), $this->path); - $this->package->Components[] = $Component; + $Component = new Package\Component(); + $Component->Name = Functions::removeBasename($item->getPathname(), $this->path); + $this->package->Components[] = $Component; - Console::outVerbose(sprintf('Found component %s', $Component->Name)); - } + Console::outVerbose(sprintf('Found component %s', $Component->Name)); + } - if(count($this->package->Components) > 0) - { - Console::outVerbose(count($this->package->Components) . ' component(s) found'); + if(count($this->package->Components) > 0) + { + Console::outVerbose(count($this->package->Components) . ' component(s) found'); + } + else + { + Console::outVerbose('No components found'); + } + + // Clear previous excludes and includes + $DirectoryScanner->setExcludes(); + $DirectoryScanner->setIncludes(); + + // Ignore component files + if($selected_build_configuration->ExcludeFiles !== null && count($selected_build_configuration->ExcludeFiles) > 0) + { + $DirectoryScanner->setExcludes(array_merge($selected_build_configuration->ExcludeFiles, ComponentFileExtensions::Php)); + } + else + { + $DirectoryScanner->setExcludes(ComponentFileExtensions::Php); + } + + Console::outVerbose('Scanning for resources... '); + /** @var SplFileInfo $item */ + foreach($DirectoryScanner($source_path) as $item) + { + // Ignore directories, they're not important. :-) + if(is_dir($item->getPathName())) + continue; + + $Resource = new Package\Resource(); + $Resource->Name = Functions::removeBasename($item->getPathname(), $this->path); + $this->package->Resources[] = $Resource; + + Console::outVerbose(sprintf('found resource %s', $Resource->Name)); + } + + if(count($this->package->Resources) > 0) + { + Console::outVerbose(count($this->package->Resources) . ' resources(s) found'); + } + else + { + Console::outVerbose('No resources found'); + } } else { - Console::outVerbose('No components found'); - } - - // Clear previous excludes and includes - $DirectoryScanner->setExcludes([]); - $DirectoryScanner->setIncludes([]); - - // Ignore component files - if($selected_build_configuration->ExcludeFiles !== null && count($selected_build_configuration->ExcludeFiles) > 0) - { - $DirectoryScanner->setExcludes(array_merge($selected_build_configuration->ExcludeFiles, ComponentFileExtensions::Php)); - } - else - { - $DirectoryScanner->setExcludes(ComponentFileExtensions::Php); - } - - Console::outVerbose('Scanning for resources... '); - /** @var SplFileInfo $item */ - foreach($DirectoryScanner($source_path) as $item) - { - // Ignore directories, they're not important. :-) - if(is_dir($item->getPathName())) - continue; - - $Resource = new Package\Resource(); - $Resource->Name = Functions::removeBasename($item->getPathname(), $this->path); - $this->package->Resources[] = $Resource; - - Console::outVerbose(sprintf('found resource %s', $Resource->Name)); - } - - if(count($this->package->Resources) > 0) - { - Console::outVerbose(count($this->package->Resources) . ' resources(s) found'); - } - else - { - Console::outVerbose('No resources found'); + Console::outWarning('Source path does not exist, skipping resource and component scanning'); } $selected_dependencies = []; @@ -206,7 +242,6 @@ $lib_path = $selected_build_configuration->OutputPath . DIRECTORY_SEPARATOR . 'libs'; if($filesystem->exists($lib_path)) $filesystem->remove($lib_path); - $filesystem->mkdir($lib_path); Console::outVerbose('Scanning for dependencies... '); foreach($selected_dependencies as $dependency) @@ -222,6 +257,10 @@ $package = $package_lock_manager->getPackageLock()->getPackage($dependency->Name); $version = $package->getVersion($dependency->Version); Console::outDebug(sprintf('copying shadow package %s=%s to %s', $dependency->Name, $dependency->Version, $out_path)); + + if(!$filesystem->exists($lib_path)) + $filesystem->mkdir($lib_path); + $filesystem->copy($version->Location, $out_path); $dependency->Source = 'libs' . DIRECTORY_SEPARATOR . sprintf('%s=%s.lib', $dependency->Name, $dependency->Version); @@ -242,7 +281,7 @@ break; } - $this->package->Dependencies[] = $dependency; + $this->package->addDependency($dependency); } if(count($this->package->Dependencies) > 0) @@ -265,7 +304,6 @@ * @throws BuildException * @throws FileNotFoundException * @throws IOException - * @throws UnsupportedRunnerException */ public function build(): ?Package { @@ -404,11 +442,11 @@ * @throws AccessDeniedException * @throws FileNotFoundException * @throws IOException - * @throws UnsupportedRunnerException + * @throws RunnerExecutionException */ public function compileExecutionPolicies(): void { - PackageCompiler::compileExecutionPolicies($this->path, $this->project); + $this->package->ExecutionUnits = PackageCompiler::compileExecutionPolicies($this->path, $this->project); } /** diff --git a/src/ncc/Classes/PhpExtension/PhpInstaller.php b/src/ncc/Classes/PhpExtension/PhpInstaller.php index 611f21e..323df19 100644 --- a/src/ncc/Classes/PhpExtension/PhpInstaller.php +++ b/src/ncc/Classes/PhpExtension/PhpInstaller.php @@ -1,4 +1,24 @@ package->Header->Options !== null && isset($this->package->Header->Options['static_files'])) - { - $static_files = $this->package->Header->Options['static_files']; - $static_files_path = $installationPaths->getBinPath() . DIRECTORY_SEPARATOR . 'static_autoload.bin'; - - foreach($static_files as $file) - { - if(!file_exists($file)) - throw new InstallationException(sprintf('Static file %s does not exist', $file)); - } - - $static_files_exists = true; - IO::fwrite($static_files_path, ZiProto::encode($static_files)); - } - $autoload_path = $installationPaths->getBinPath() . DIRECTORY_SEPARATOR . 'autoload.php'; - $autoload_src = $this->generateAutoload($installationPaths->getSourcePath(), $autoload_path, $static_files_exists); + $autoload_src = $this->generateAutoload($installationPaths->getSourcePath(), $autoload_path); IO::fwrite($autoload_path, $autoload_src); } @@ -286,15 +288,13 @@ * * @param string $src * @param string $output - * @param bool $ignore_units * @return string * @throws AccessDeniedException * @throws CollectorException * @throws FileNotFoundException * @throws IOException - * @throws NoUnitsFoundException */ - private function generateAutoload(string $src, string $output, bool $ignore_units=false): string + private function generateAutoload(string $src, string $output): string { // Construct configuration $configuration = new Config([$src]); @@ -315,10 +315,6 @@ $result = self::runCollector($factory, $configuration); // Exception raises when there are no files in the project that can be processed by the autoloader - if(!$result->hasUnits() && !$ignore_units) - { - throw new NoUnitsFoundException('No units were found in the project'); - } $template = IO::fread($configuration->getTemplate()); diff --git a/src/ncc/Classes/PhpExtension/PhpRunner.php b/src/ncc/Classes/PhpExtension/PhpRunner.php index 100a807..89f9898 100644 --- a/src/ncc/Classes/PhpExtension/PhpRunner.php +++ b/src/ncc/Classes/PhpExtension/PhpRunner.php @@ -1,18 +1,33 @@ Execute->Target = null; $execution_unit->ExecutionPolicy = $policy; - $execution_unit->Data = Base64::encode(IO::fread($target_file)); + $execution_unit->Data = IO::fread($path); return $execution_unit; } @@ -47,21 +61,4 @@ { return '.php'; } - - /** - * @param ExecutionPointer $pointer - * @return Process - * @throws RunnerExecutionException - */ - public static function prepareProcess(ExecutionPointer $pointer): Process - { - $php_bin = new ExecutableFinder(); - $php_bin = $php_bin->find('php'); - if($php_bin == null) - throw new RunnerExecutionException('Cannot locate PHP executable'); - - if($pointer->ExecutionPolicy->Execute->Options !== null && count($pointer->ExecutionPolicy->Execute->Options) > 0) - return new Process(array_merge([$php_bin, $pointer->FilePointer], $pointer->ExecutionPolicy->Execute->Options)); - return new Process([$php_bin, $pointer->FilePointer]); - } } \ No newline at end of file diff --git a/src/ncc/Classes/PhpExtension/PhpRuntime.php b/src/ncc/Classes/PhpExtension/PhpRuntime.php new file mode 100644 index 0000000..97d3df1 --- /dev/null +++ b/src/ncc/Classes/PhpExtension/PhpRuntime.php @@ -0,0 +1,124 @@ +getInstallPaths()->getBinPath() . DIRECTORY_SEPARATOR . 'autoload.php'; + $static_files = $versionEntry->getInstallPaths()->getBinPath() . DIRECTORY_SEPARATOR . 'static_autoload.bin'; + $constants_path = $versionEntry->getInstallPaths()->getDataPath() . DIRECTORY_SEPARATOR . 'const'; + $assembly_path = $versionEntry->getInstallPaths()->getDataPath() . DIRECTORY_SEPARATOR . 'assembly'; + + if(!file_exists($assembly_path)) + throw new ImportException('Cannot locate assembly file \'' . $assembly_path . '\''); + + try + { + $assembly_content = ZiProto::decode(IO::fread($assembly_path)); + $assembly = Assembly::fromArray($assembly_content); + } + catch(Exception $e) + { + throw new ImportException('Failed to load assembly file \'' . $assembly_path . '\': ' . $e->getMessage()); + } + + if(file_exists($constants_path)) + { + try + { + $constants = ZiProto::decode(IO::fread($constants_path)); + } + catch(Exception $e) + { + throw new ImportException('Failed to load constants file \'' . $constants_path . '\': ' . $e->getMessage()); + } + + foreach($constants as $name => $value) + { + $value = ConstantCompiler::compileRuntimeConstants($value); + + try + { + Constants::register($assembly->Package, $name, $value, true); + } + catch (ConstantReadonlyException $e) + { + trigger_error('Constant \'' . $name . '\' is readonly (' . $assembly->Package . ')', E_USER_WARNING); + } + catch (InvalidConstantNameException $e) + { + throw new ImportException('Invalid constant name \'' . $name . '\' (' . $assembly->Package . ')', $e); + } + } + } + + if(file_exists($autoload_path) && !in_array(RuntimeImportOptions::ImportAutoloader, $options)) + { + require_once($autoload_path); + } + + if(file_exists($static_files) && !in_array(RuntimeImportOptions::ImportStaticFiles, $options)) + { + try + { + $static_files = ZiProto::decode(IO::fread($static_files)); + foreach($static_files as $file) + require_once($file); + } + catch(Exception $e) + { + throw new ImportException('Failed to load static files: ' . $e->getMessage(), $e); + } + + } + + if(!file_exists($autoload_path) && !file_exists($static_files)) + return false; + + return true; + } + } \ No newline at end of file diff --git a/src/ncc/Classes/PythonExtension/Python2Runner.php b/src/ncc/Classes/PythonExtension/Python2Runner.php new file mode 100644 index 0000000..b411a06 --- /dev/null +++ b/src/ncc/Classes/PythonExtension/Python2Runner.php @@ -0,0 +1,56 @@ +Execute->Target = null; + $execution_unit->ExecutionPolicy = $policy; + $execution_unit->Data = IO::fread($path); + + return $execution_unit; + } + + /** + * @inheritDoc + */ + public static function getFileExtension(): string + { + return '.py'; + } + } \ No newline at end of file diff --git a/src/ncc/Classes/PythonExtension/Python3Runner.php b/src/ncc/Classes/PythonExtension/Python3Runner.php new file mode 100644 index 0000000..bc6ff39 --- /dev/null +++ b/src/ncc/Classes/PythonExtension/Python3Runner.php @@ -0,0 +1,56 @@ +Execute->Target = null; + $execution_unit->ExecutionPolicy = $policy; + $execution_unit->Data = IO::fread($path); + + return $execution_unit; + } + + /** + * @inheritDoc + */ + public static function getFileExtension(): string + { + return '.py'; + } + } \ No newline at end of file diff --git a/src/ncc/Classes/PythonExtension/PythonRunner.php b/src/ncc/Classes/PythonExtension/PythonRunner.php new file mode 100644 index 0000000..b3d5168 --- /dev/null +++ b/src/ncc/Classes/PythonExtension/PythonRunner.php @@ -0,0 +1,56 @@ +Execute->Target = null; + $execution_unit->ExecutionPolicy = $policy; + $execution_unit->Data = IO::fread($path); + + return $execution_unit; + } + + /** + * @inheritDoc + */ + public static function getFileExtension(): string + { + return '.py'; + } +} \ No newline at end of file diff --git a/src/ncc/Exceptions/AccessDeniedException.php b/src/ncc/Exceptions/AccessDeniedException.php index 96bb695..f1af713 100644 --- a/src/ncc/Exceptions/AccessDeniedException.php +++ b/src/ncc/Exceptions/AccessDeniedException.php @@ -1,6 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/BuildException.php b/src/ncc/Exceptions/BuildException.php index 7fbb3d8..4394f96 100644 --- a/src/ncc/Exceptions/BuildException.php +++ b/src/ncc/Exceptions/BuildException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/ComponentChecksumException.php b/src/ncc/Exceptions/ComponentChecksumException.php index 68bb520..9261aa7 100644 --- a/src/ncc/Exceptions/ComponentChecksumException.php +++ b/src/ncc/Exceptions/ComponentChecksumException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/ComponentDecodeException.php b/src/ncc/Exceptions/ComponentDecodeException.php index 593255f..6d08524 100644 --- a/src/ncc/Exceptions/ComponentDecodeException.php +++ b/src/ncc/Exceptions/ComponentDecodeException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/ComponentVersionNotFoundException.php b/src/ncc/Exceptions/ComponentVersionNotFoundException.php index ae57404..8edbc99 100644 --- a/src/ncc/Exceptions/ComponentVersionNotFoundException.php +++ b/src/ncc/Exceptions/ComponentVersionNotFoundException.php @@ -1,6 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/ComposerException.php b/src/ncc/Exceptions/ComposerException.php index 9e37388..52bc4b7 100644 --- a/src/ncc/Exceptions/ComposerException.php +++ b/src/ncc/Exceptions/ComposerException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/ComposerNotAvailableException.php b/src/ncc/Exceptions/ComposerNotAvailableException.php index 2fa1d4b..2f99a51 100644 --- a/src/ncc/Exceptions/ComposerNotAvailableException.php +++ b/src/ncc/Exceptions/ComposerNotAvailableException.php @@ -1,20 +1,32 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/ConstantReadonlyException.php b/src/ncc/Exceptions/ConstantReadonlyException.php index 5930827..9ccbeef 100644 --- a/src/ncc/Exceptions/ConstantReadonlyException.php +++ b/src/ncc/Exceptions/ConstantReadonlyException.php @@ -1,6 +1,26 @@ code = ExceptionCodes::DirectoryNotFoundException; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/ExecutionUnitNotFoundException.php b/src/ncc/Exceptions/ExecutionUnitNotFoundException.php deleted file mode 100644 index 228f512..0000000 --- a/src/ncc/Exceptions/ExecutionUnitNotFoundException.php +++ /dev/null @@ -1,8 +0,0 @@ -code = ExceptionCodes::FileNotFoundException; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/GitCheckoutException.php b/src/ncc/Exceptions/GitCheckoutException.php new file mode 100644 index 0000000..aaad3c4 --- /dev/null +++ b/src/ncc/Exceptions/GitCheckoutException.php @@ -0,0 +1,39 @@ +message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/ImportException.php b/src/ncc/Exceptions/ImportException.php new file mode 100644 index 0000000..3e99de3 --- /dev/null +++ b/src/ncc/Exceptions/ImportException.php @@ -0,0 +1,39 @@ +message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/InternalComposerNotAvailableException.php b/src/ncc/Exceptions/InternalComposerNotAvailableException.php index 718114b..1d4808c 100644 --- a/src/ncc/Exceptions/InternalComposerNotAvailableException.php +++ b/src/ncc/Exceptions/InternalComposerNotAvailableException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/InvalidBuildConfigurationException.php b/src/ncc/Exceptions/InvalidBuildConfigurationException.php new file mode 100644 index 0000000..80cb3cc --- /dev/null +++ b/src/ncc/Exceptions/InvalidBuildConfigurationException.php @@ -0,0 +1,39 @@ +message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/InvalidCredentialsEntryException.php b/src/ncc/Exceptions/InvalidCredentialsEntryException.php index 22bf01c..5b0d6db 100644 --- a/src/ncc/Exceptions/InvalidCredentialsEntryException.php +++ b/src/ncc/Exceptions/InvalidCredentialsEntryException.php @@ -1,6 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/InvalidPackageNameException.php b/src/ncc/Exceptions/InvalidPackageNameException.php index da9ebaa..fbaeb18 100644 --- a/src/ncc/Exceptions/InvalidPackageNameException.php +++ b/src/ncc/Exceptions/InvalidPackageNameException.php @@ -1,6 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/InvalidProjectConfigurationException.php b/src/ncc/Exceptions/InvalidProjectConfigurationException.php index 4923365..a8ec2bf 100644 --- a/src/ncc/Exceptions/InvalidProjectConfigurationException.php +++ b/src/ncc/Exceptions/InvalidProjectConfigurationException.php @@ -1,6 +1,26 @@ message = $message; - $this->previous = $previous; $this->property = $property; } + + /** + * @return string|null + */ + public function getProperty(): ?string + { + return $this->property; + } } \ No newline at end of file diff --git a/src/ncc/Exceptions/InvalidProjectNameException.php b/src/ncc/Exceptions/InvalidProjectNameException.php index 8778bce..bf3adff 100644 --- a/src/ncc/Exceptions/InvalidProjectNameException.php +++ b/src/ncc/Exceptions/InvalidProjectNameException.php @@ -1,6 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/InvalidVersionNumberException.php b/src/ncc/Exceptions/InvalidVersionNumberException.php index 708c16a..23b1e8f 100644 --- a/src/ncc/Exceptions/InvalidVersionNumberException.php +++ b/src/ncc/Exceptions/InvalidVersionNumberException.php @@ -1,6 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/NoAvailableUnitsException.php b/src/ncc/Exceptions/NoAvailableUnitsException.php index 56d61ac..eb7f325 100644 --- a/src/ncc/Exceptions/NoAvailableUnitsException.php +++ b/src/ncc/Exceptions/NoAvailableUnitsException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/NoUnitsFoundException.php b/src/ncc/Exceptions/NoUnitsFoundException.php index a59e3da..48423b7 100644 --- a/src/ncc/Exceptions/NoUnitsFoundException.php +++ b/src/ncc/Exceptions/NoUnitsFoundException.php @@ -1,6 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/NotImplementedException.php b/src/ncc/Exceptions/NotImplementedException.php index e53d390..55de1d1 100644 --- a/src/ncc/Exceptions/NotImplementedException.php +++ b/src/ncc/Exceptions/NotImplementedException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/NotSupportedException.php b/src/ncc/Exceptions/NotSupportedException.php new file mode 100644 index 0000000..a629e67 --- /dev/null +++ b/src/ncc/Exceptions/NotSupportedException.php @@ -0,0 +1,39 @@ +message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/PackageFetchException.php b/src/ncc/Exceptions/PackageFetchException.php new file mode 100644 index 0000000..4dbbbed --- /dev/null +++ b/src/ncc/Exceptions/PackageFetchException.php @@ -0,0 +1,39 @@ +message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/PackageNotFoundException.php b/src/ncc/Exceptions/PackageNotFoundException.php index 88bd370..070d601 100644 --- a/src/ncc/Exceptions/PackageNotFoundException.php +++ b/src/ncc/Exceptions/PackageNotFoundException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/PackageParsingException.php b/src/ncc/Exceptions/PackageParsingException.php index 70c1be7..7106d1a 100644 --- a/src/ncc/Exceptions/PackageParsingException.php +++ b/src/ncc/Exceptions/PackageParsingException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/PackagePreparationFailedException.php b/src/ncc/Exceptions/PackagePreparationFailedException.php index 5a45e3e..223795f 100644 --- a/src/ncc/Exceptions/PackagePreparationFailedException.php +++ b/src/ncc/Exceptions/PackagePreparationFailedException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/ProjectAlreadyExistsException.php b/src/ncc/Exceptions/ProjectAlreadyExistsException.php index b1d5f6c..3354002 100644 --- a/src/ncc/Exceptions/ProjectAlreadyExistsException.php +++ b/src/ncc/Exceptions/ProjectAlreadyExistsException.php @@ -1,6 +1,26 @@ message = $message; - $this->code = $code; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/RunnerExecutionException.php b/src/ncc/Exceptions/RunnerExecutionException.php index e568dd1..244a0b5 100644 --- a/src/ncc/Exceptions/RunnerExecutionException.php +++ b/src/ncc/Exceptions/RunnerExecutionException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/RuntimeException.php b/src/ncc/Exceptions/RuntimeException.php index a3ddb60..feb0953 100644 --- a/src/ncc/Exceptions/RuntimeException.php +++ b/src/ncc/Exceptions/RuntimeException.php @@ -1,6 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/UnsupportedArchiveException.php b/src/ncc/Exceptions/UnsupportedArchiveException.php new file mode 100644 index 0000000..4a7bf33 --- /dev/null +++ b/src/ncc/Exceptions/UnsupportedArchiveException.php @@ -0,0 +1,39 @@ +message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/UnsupportedComponentTypeException.php b/src/ncc/Exceptions/UnsupportedComponentTypeException.php index ecd4c79..5352a41 100644 --- a/src/ncc/Exceptions/UnsupportedComponentTypeException.php +++ b/src/ncc/Exceptions/UnsupportedComponentTypeException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/UnsupportedExtensionVersionException.php b/src/ncc/Exceptions/UnsupportedExtensionVersionException.php index f24b429..4084580 100644 --- a/src/ncc/Exceptions/UnsupportedExtensionVersionException.php +++ b/src/ncc/Exceptions/UnsupportedExtensionVersionException.php @@ -1,13 +1,39 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/UnsupportedProjectTypeException.php b/src/ncc/Exceptions/UnsupportedProjectTypeException.php new file mode 100644 index 0000000..6cb7904 --- /dev/null +++ b/src/ncc/Exceptions/UnsupportedProjectTypeException.php @@ -0,0 +1,40 @@ +message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Exceptions/VersionNotFoundException.php b/src/ncc/Exceptions/VersionNotFoundException.php index b7b4acc..c1348ea 100644 --- a/src/ncc/Exceptions/VersionNotFoundException.php +++ b/src/ncc/Exceptions/VersionNotFoundException.php @@ -1,8 +1,26 @@ message = $message; - $this->previous = $previous; } } \ No newline at end of file diff --git a/src/ncc/Extensions/ZiProto/Abstracts/Options.php b/src/ncc/Extensions/ZiProto/Abstracts/Options.php index 58534f0..40c0cdb 100644 --- a/src/ncc/Extensions/ZiProto/Abstracts/Options.php +++ b/src/ncc/Extensions/ZiProto/Abstracts/Options.php @@ -1,6 +1,26 @@ decodeStrData($this->decodeUint8()); + case 0xda: case 0xc5: return $this->decodeStrData($this->decodeUint16()); + case 0xdb: case 0xc6: return $this->decodeStrData($this->decodeUint32()); // float @@ -210,9 +234,6 @@ case 0xd3: return $this->decodeInt64(); // str - case 0xd9: return $this->decodeStrData($this->decodeUint8()); - case 0xda: return $this->decodeStrData($this->decodeUint16()); - case 0xdb: return $this->decodeStrData($this->decodeUint32()); // array case 0xdc: return $this->decodeArrayData($this->decodeUint16()); @@ -351,7 +372,7 @@ } /** - * @return bool|string + * @return string */ public function decodeStr() { @@ -387,7 +408,7 @@ } /** - * @return bool|string + * @return string */ public function decodeBin() { diff --git a/src/ncc/Extensions/ZiProto/DecodingOptions.php b/src/ncc/Extensions/ZiProto/DecodingOptions.php index d2fc7f8..347b2a1 100644 --- a/src/ncc/Extensions/ZiProto/DecodingOptions.php +++ b/src/ncc/Extensions/ZiProto/DecodingOptions.php @@ -1,5 +1,26 @@ bigIntMode = self::getSingleOption('bigint', $bitmask, + $self->bigIntMode = self::getSingleOption($bitmask, Options::BIGINT_AS_STR | Options::BIGINT_AS_GMP | Options::BIGINT_AS_EXCEPTION @@ -66,12 +87,11 @@ } /** - * @param string $name * @param int $bitmask * @param int $validBitmask * @return int */ - private static function getSingleOption(string $name, int $bitmask, int $validBitmask) : int + private static function getSingleOption(int $bitmask, int $validBitmask) : int { $option = $bitmask & $validBitmask; if ($option === ($option & -$option)) @@ -91,6 +111,6 @@ $validOptions[] = __CLASS__.'::'.$map[$i]; } - throw InvalidOptionException::outOfRange($name, $validOptions); + throw InvalidOptionException::outOfRange('bigint', $validOptions); } } diff --git a/src/ncc/Extensions/ZiProto/EncodingOptions.php b/src/ncc/Extensions/ZiProto/EncodingOptions.php index 2525641..c713ba4 100644 --- a/src/ncc/Extensions/ZiProto/EncodingOptions.php +++ b/src/ncc/Extensions/ZiProto/EncodingOptions.php @@ -1,6 +1,26 @@ = 0) { @@ -237,7 +257,7 @@ * @param $float * @return string */ - public function encodeFloat($float) + public function encodeFloat($float): string { return $this->isForceFloat32 ? "\xca". pack('G', $float) @@ -248,7 +268,7 @@ * @param $str * @return string */ - public function encodeStr($str) + public function encodeStr($str): string { $length = strlen($str); @@ -274,7 +294,7 @@ * @param $str * @return string */ - public function encodeBin($str) + public function encodeBin($str): string { $length = strlen($str); @@ -293,9 +313,9 @@ /** * @param $array - * @return false|string + * @return string */ - public function encodeArray($array) + public function encodeArray($array): string { $data = $this->encodeArrayHeader(count($array)); @@ -311,7 +331,7 @@ * @param $size * @return string */ - public function encodeArrayHeader($size) + public function encodeArrayHeader($size): string { if ($size <= 0xf) { @@ -328,9 +348,9 @@ /** * @param $map - * @return false|string + * @return string */ - public function encodeMap($map) + public function encodeMap($map): string { $data = $this->encodeMapHeader(count($map)); @@ -371,7 +391,7 @@ * @param $size * @return string */ - public function encodeMapHeader($size) + public function encodeMapHeader($size): string { if ($size <= 0xf) { @@ -391,7 +411,7 @@ * @param $data * @return string */ - public function encodeExt($type, $data) + public function encodeExt($type, $data): string { $length = strlen($data); diff --git a/src/ncc/Extensions/ZiProto/Type/Binary.php b/src/ncc/Extensions/ZiProto/Type/Binary.php index 2bb9078..bb8b85d 100644 --- a/src/ncc/Extensions/ZiProto/Type/Binary.php +++ b/src/ncc/Extensions/ZiProto/Type/Binary.php @@ -1,6 +1,26 @@ Configuration = RuntimeCache::get('ncc.yaml'); if($this->Configuration !== null) return; @@ -67,6 +89,8 @@ */ public function save(): void { + Console::outDebug(sprintf('saving configuration file to %s', PathFinder::getConfigurationFile())); + if(Resolver::resolveScope() !== Scopes::System) throw new AccessDeniedException('Cannot save configuration file, insufficient permissions'); @@ -88,6 +112,9 @@ */ public function getProperty(string $property) { + Console::outDebug(sprintf('getting property %s', $property)); + + Console::outDebug($property); $current_selection = $this->getConfiguration(); foreach(explode('.', strtolower($property)) as $property) { @@ -119,202 +146,20 @@ */ public function updateProperty(string $property, $value): bool { - // composer.options.quiet - $result = match (strtolower(explode('.', $property)[0])) - { - 'ncc' => $this->updateNccProperties($property, $value), - 'php' => $this->updatePhpProperties($property, $value), - 'git' => $this->updateGitProperties($property, $value), - 'runners' => $this->updateRunnerProperties($property, $value), - 'composer' => $this->updateComposerProperties($property, $value), - default => false, - }; + Console::outDebug(sprintf('updating property %s', $property)); + $keys = explode('.', $property); + $current = &$this->Configuration; + foreach ($keys as $k) + { + if (!array_key_exists($k, $current)) + { + return false; + } + $current = &$current[$k]; + } + $current = Functions::stringTypeCast($value); $this->save(); - return $result; - } - - /** - * Updates NCC configuration properties in the configuration - * - * @param string $property - * @param $value - * @return bool - */ - private function updateNccProperties(string $property, $value): bool - { - $delete = false; - if(is_null($value)) - $delete = true; - - switch(strtolower($property)) - { - case 'ncc.cli.no_colors': - $this->Configuration['ncc']['cli']['no_colors'] = Functions::cbool($value); - break; - case 'ncc.cli.basic_ascii': - $this->Configuration['ncc']['cli']['basic_ascii'] = Functions::cbool($value); - break; - case 'ncc.cli.logging': - $this->Configuration['ncc']['cli']['logging'] = ($delete ? LogLevel::Info : (string)$value); - break; - - default: - return false; - } - - return true; - } - - /** - * Updates PHP properties in the configuraiton - * - * @param string $property - * @param $value - * @return bool - */ - private function updatePhpProperties(string $property, $value): bool - { - $delete = false; - if(is_null($value)) - $delete = true; - - switch(strtolower($property)) - { - case 'php.executable_path': - $this->Configuration['php']['executable_path'] = ($delete ? null : (string)$value); - break; - case 'php.runtime.initialize_on_require': - $this->Configuration['php']['runtime']['initialize_on_require'] = Functions::cbool($value); - break; - case 'php.runtime.handle_exceptions': - $this->Configuration['php']['runtime']['handle_exceptions'] = Functions::cbool($value); - break; - - default: - return false; - } - - return true; - } - - /** - * Updated git properties - * - * @param string $property - * @param $value - * @return bool - */ - private function updateGitProperties(string $property, $value): bool - { - $delete = false; - if(is_null($value)) - $delete = true; - - switch(strtolower($property)) - { - case 'git.enabled': - $this->Configuration['git']['enabled'] = Functions::cbool($value); - break; - case 'git.executable_path': - $this->Configuration['git']['executable_path'] = ($delete? null : (string)$value); - break; - default: - return false; - } - - return true; - } - - /** - * Updaters runner properties - * - * @param string $property - * @param $value - * @return bool - */ - private function updateRunnerProperties(string $property, $value): bool - { - $delete = false; - if(is_null($value)) - $delete = true; - - switch(strtolower($property)) - { - case 'runners.php': - $this->Configuration['runners']['php'] = ($delete? null : (string)$value); - break; - case 'runners.bash': - $this->Configuration['runners']['bash'] = ($delete? null : (string)$value); - break; - case 'runners.sh': - $this->Configuration['runners']['sh'] = ($delete? null : (string)$value); - break; - case 'runners.python': - $this->Configuration['runners']['python'] = ($delete? null : (string)$value); - break; - case 'runners.python3': - $this->Configuration['runners']['python3'] = ($delete? null : (string)$value); - break; - case 'runners.python2': - $this->Configuration['runners']['python2'] = ($delete? null : (string)$value); - break; - - default: - return false; - } - - return true; - } - - /** - * Updates a composer property value - * - * @param string $property - * @param $value - * @return bool - */ - private function updateComposerProperties(string $property, $value): bool - { - $delete = false; - if(is_null($value)) - $delete = true; - - switch(strtolower($property)) - { - case 'composer.enabled': - $this->Configuration['composer']['enabled'] = Functions::cbool($value); - break; - case 'composer.enable_internal_composer': - $this->Configuration['composer']['enable_internal_composer'] = Functions::cbool($value); - break; - case 'composer.executable_path': - $this->Configuration['composer']['executable_path'] = ($delete? null : (string)$value); - break; - case 'composer.options.quiet': - $this->Configuration['composer']['options']['quiet'] = Functions::cbool($value); - break; - case 'composer.options.no_ansi': - $this->Configuration['composer']['options']['no_ansi'] = Functions::cbool($value); - break; - case 'composer.options.no_interaction': - $this->Configuration['composer']['options']['no_interaction'] = Functions::cbool($value); - break; - case 'composer.options.profile': - $this->Configuration['composer']['options']['profile'] = Functions::cbool($value); - break; - case 'composer.options.no_scripts': - $this->Configuration['composer']['options']['no_scripts'] = Functions::cbool($value); - break; - case 'composer.options.no_cache': - $this->Configuration['composer']['options']['no_cache'] = Functions::cbool($value); - break; - case 'composer.options.logging': - $this->Configuration['composer']['options']['logging'] = ((int)$value > 0 ? (int)$value : 1); - break; - default: - return false; - } return true; } diff --git a/src/ncc/Managers/CredentialManager.php b/src/ncc/Managers/CredentialManager.php index 4a9b2c2..679b14d 100644 --- a/src/ncc/Managers/CredentialManager.php +++ b/src/ncc/Managers/CredentialManager.php @@ -1,4 +1,24 @@ CredentialsPath = PathFinder::getDataPath(Scopes::System) . DIRECTORY_SEPARATOR . 'credentials.store'; - } + $this->Vault = null; - /** - * Determines if CredentialManager has correct access to manage credentials on the system - * - * @return bool - */ - public function checkAccess(): bool - { - $ResolvedScope = Resolver::resolveScope(); - - if($ResolvedScope !== Scopes::System) + try { - return False; + $this->loadVault(); + } + catch(Exception $e) + { + unset($e); } - return True; + if($this->Vault == null) + $this->Vault = new Vault(); } /** @@ -59,101 +82,86 @@ */ public function constructStore(): void { + Console::outDebug(sprintf('constructing credentials store at %s', $this->CredentialsPath)); + // Do not continue the function if the file already exists, if the file is damaged a separate function // is to be executed to fix the damaged file. if(file_exists($this->CredentialsPath)) return; - if(!$this->checkAccess()) - { + if(Resolver::resolveScope() !== Scopes::System) throw new AccessDeniedException('Cannot construct credentials store without system permissions'); - } $VaultObject = new Vault(); $VaultObject->Version = Versions::CredentialsStoreVersion; - IO::fwrite($this->CredentialsPath, ZiProto::encode($VaultObject->toArray()), 0600); + IO::fwrite($this->CredentialsPath, ZiProto::encode($VaultObject->toArray()), 0744); } /** - * Returns the vault object from the credentials store file. + * Loads the vault from the disk * - * @return Vault - * @throws AccessDeniedException - * @throws IOException - * @throws RuntimeException - */ - public function getVault(): Vault - { - $this->constructStore(); - - if(!$this->checkAccess()) - { - throw new AccessDeniedException('Cannot read credentials store without system permissions'); - } - - try - { - $Vault = ZiProto::decode(IO::fread($this->CredentialsPath)); - } - catch(Exception $e) - { - // TODO: Implement error-correction for corrupted credentials store. - throw new RuntimeException($e->getMessage(), $e); - } - - return Vault::fromArray($Vault); - } - - /** - * Saves the vault object to the credentials store - * - * @param Vault $vault * @return void * @throws AccessDeniedException * @throws IOException + * @throws RuntimeException + * @throws FileNotFoundException */ - public function saveVault(Vault $vault): void + private function loadVault(): void { - if(!$this->checkAccess()) + Console::outDebug(sprintf('loading credentials store from %s', $this->CredentialsPath)); + + if($this->Vault !== null) + return; + + if(!file_exists($this->CredentialsPath)) { - throw new AccessDeniedException('Cannot write to credentials store without system permissions'); + $this->Vault = new Vault(); + return; } - IO::fwrite($this->CredentialsPath, ZiProto::encode($vault->toArray()), 0600); + $VaultArray = ZiProto::decode(IO::fread($this->CredentialsPath)); + $VaultObject = Vault::fromArray($VaultArray); + + if($VaultObject->Version !== Versions::CredentialsStoreVersion) + throw new RuntimeException('Credentials store version mismatch'); + + $this->Vault = $VaultObject; } /** - * Registers an entry to the credentials store file + * Saves the vault to the disk * - * @param Vault\Entry $entry * @return void * @throws AccessDeniedException - * @throws InvalidCredentialsEntryException - * @throws RuntimeException * @throws IOException + * @noinspection PhpUnused */ - public function registerEntry(Vault\Entry $entry): void + public function saveVault(): void { - if(!preg_match('/^[\w-]+$/', $entry->Alias)) - { - throw new InvalidCredentialsEntryException('The property \'Alias\' must be alphanumeric (Regex error)'); - } + Console::outDebug(sprintf('saving credentials store to %s', $this->CredentialsPath)); - // TODO: Implement more validation checks for the rest of the entry properties. - // TODO: Implement encryption for entries that require encryption (For securing passwords and data) + if(Resolver::resolveScope() !== Scopes::System) + throw new AccessDeniedException('Cannot save credentials store without system permissions'); - $Vault = $this->getVault(); - $Vault->Entries[] = $entry; - - $this->saveVault($Vault); + IO::fwrite($this->CredentialsPath, ZiProto::encode($this->Vault->toArray()), 0744); } + /** - * @return null + * @return string + * @noinspection PhpUnused */ - public function getCredentialsPath(): ?string + public function getCredentialsPath(): string { return $this->CredentialsPath; } + + /** + * @return Vault|null + */ + public function getVault(): ?Vault + { + return $this->Vault; + } } \ No newline at end of file diff --git a/src/ncc/Managers/ExecutionPointerManager.php b/src/ncc/Managers/ExecutionPointerManager.php index c2f2dbb..9bedc3b 100644 --- a/src/ncc/Managers/ExecutionPointerManager.php +++ b/src/ncc/Managers/ExecutionPointerManager.php @@ -1,4 +1,24 @@ TemporaryUnits) == 0) return; + Console::outVerbose('Cleaning temporary units...'); + try { foreach($this->TemporaryUnits as $datum) { + Console::outDebug(sprintf('deleting unit %s=%s.%s', $datum['package'], $datum['version'], $datum['name'])); $this->removeUnit($datum['package'], $datum['version'], $datum['name']); } } @@ -100,9 +128,31 @@ */ private function getPackageId(string $package, string $version): string { + Console::outDebug(sprintf('calculating package id for %s=%s', $package, $version)); return hash('haval128,4', $package . $version); } + /** + * Returns the path to the execution pointer file + * + * @param string $package + * @param string $version + * @param string $name + * @return string + * @throws FileNotFoundException + */ + public function getEntryPointPath(string $package, string $version, string $name): string + { + $package_id = $this->getPackageId($package, $version); + $package_bin_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id; + $entry_point_path = $package_bin_path . DIRECTORY_SEPARATOR . hash('haval128,4', $name) . '.entrypoint'; + + if(!file_exists($entry_point_path)) + throw new FileNotFoundException('Cannot find entry point for ' . $package . '=' . $version . '.' . $name); + + return $entry_point_path; + } + /** * Adds a new Execution Unit to the * @@ -114,7 +164,7 @@ * @throws AccessDeniedException * @throws FileNotFoundException * @throws IOException - * @throws UnsupportedRunnerException + * @throws RunnerExecutionException * @noinspection PhpUnused */ public function addUnit(string $package, string $version, ExecutionUnit $unit, bool $temporary=false): void @@ -122,9 +172,17 @@ if(Resolver::resolveScope() !== Scopes::System) throw new AccessDeniedException('Cannot add new ExecutionUnit \'' . $unit->ExecutionPolicy->Name .'\' for ' . $package . ', insufficient permissions'); + Console::outVerbose(sprintf('Adding new ExecutionUnit \'%s\' for %s', $unit->ExecutionPolicy->Name, $package)); + $package_id = $this->getPackageId($package, $version); $package_config_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id . '.inx'; $package_bin_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id; + $entry_point_path = $package_bin_path . DIRECTORY_SEPARATOR . hash('haval128,4', $unit->ExecutionPolicy->Name) . '.entrypoint'; + + Console::outDebug(sprintf('package_id=%s', $package_id)); + Console::outDebug(sprintf('package_config_path=%s', $package_config_path)); + Console::outDebug(sprintf('package_bin_path=%s', $package_bin_path)); + Console::outDebug(sprintf('entry_point_path=%s', $entry_point_path)); $filesystem = new Filesystem(); @@ -139,11 +197,20 @@ } $bin_file = $package_bin_path . DIRECTORY_SEPARATOR . hash('haval128,4', $unit->ExecutionPolicy->Name); - $bin_file .= match ($unit->ExecutionPolicy->Runner) { + $bin_file .= match ($unit->ExecutionPolicy->Runner) + { + Runners::bash => BashRunner::getFileExtension(), Runners::php => PhpRunner::getFileExtension(), - default => throw new UnsupportedRunnerException('The runner \'' . $unit->ExecutionPolicy->Runner . '\' is not supported'), + Runners::perl => PerlRunner::getFileExtension(), + Runners::python => PythonRunner::getFileExtension(), + Runners::python2 => Python2Runner::getFileExtension(), + Runners::python3 => Python3Runner::getFileExtension(), + Runners::lua => LuaRunner::getFileExtension(), + default => throw new RunnerExecutionException('The runner \'' . $unit->ExecutionPolicy->Runner . '\' is not supported'), }; + Console::outDebug(sprintf('bin_file=%s', $bin_file)); + if($filesystem->exists($bin_file) && $temporary) return; @@ -157,8 +224,19 @@ $execution_pointers->addUnit($unit, $bin_file); IO::fwrite($package_config_path, ZiProto::encode($execution_pointers->toArray(true))); + $entry_point = sprintf("#!%s\nncc exec --package=\"%s\" --exec-version=\"%s\" --exec-unit=\"%s\" --exec-args \"$@\"", + '/bin/bash', + $package, $version, $unit->ExecutionPolicy->Name + ); + + if(file_exists($entry_point_path)) + $filesystem->remove($entry_point_path); + IO::fwrite($entry_point_path, $entry_point); + chmod($entry_point_path, 0755); + if($temporary) { + Console::outVerbose(sprintf('Adding temporary ExecutionUnit \'%s\' for %s', $unit->ExecutionPolicy->Name, $package)); $this->TemporaryUnits[] = [ 'package' => $package, 'version' => $version, @@ -183,10 +261,16 @@ if(Resolver::resolveScope() !== Scopes::System) throw new AccessDeniedException('Cannot remove ExecutionUnit \'' . $name .'\' for ' . $package . ', insufficient permissions'); + Console::outVerbose(sprintf('Removing ExecutionUnit \'%s\' for %s', $name, $package)); + $package_id = $this->getPackageId($package, $version); $package_config_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id . '.inx'; $package_bin_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id; + Console::outDebug(sprintf('package_id=%s', $package_id)); + Console::outDebug(sprintf('package_config_path=%s', $package_config_path)); + Console::outDebug(sprintf('package_bin_path=%s', $package_bin_path)); + $filesystem = new Filesystem(); if(!$filesystem->exists($package_config_path)) return false; @@ -225,16 +309,25 @@ */ public function getUnits(string $package, string $version): array { + Console::outVerbose(sprintf('getting execution units for %s', $package)); + $package_id = $this->getPackageId($package, $version); $package_config_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id . '.inx'; + Console::outDebug(sprintf('package_id=%s', $package_id)); + Console::outDebug(sprintf('package_config_path=%s', $package_config_path)); + if(!file_exists($package_config_path)) + { + Console::outWarning(sprintf('Path \'%s\' does not exist', $package_config_path)); return []; + } $execution_pointers = ExecutionPointers::fromArray(ZiProto::decode(IO::fread($package_config_path))); $results = []; foreach($execution_pointers->getPointers() as $pointer) { + Console::outDebug(sprintf('unit %s', $pointer->ExecutionPolicy->Name)); $results[] = $pointer->ExecutionPolicy->Name; } @@ -247,17 +340,18 @@ * @param string $package * @param string $version * @param string $name - * @return void + * @param array $args + * @return int * @throws AccessDeniedException - * @throws ExecutionUnitNotFoundException * @throws FileNotFoundException * @throws IOException * @throws NoAvailableUnitsException - * @throws UnsupportedRunnerException * @throws RunnerExecutionException */ - public function executeUnit(string $package, string $version, string $name): void + public function executeUnit(string $package, string $version, string $name, array $args=[]): int { + Console::outVerbose(sprintf('executing unit %s for %s', $name, $package)); + $package_id = $this->getPackageId($package, $version); $package_config_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id . '.inx'; @@ -268,18 +362,37 @@ $unit = $execution_pointers->getUnit($name); if($unit == null) - throw new ExecutionUnitNotFoundException('The execution unit \'' . $name . '\' was not found for \'' . $package . '=' .$version .'\''); + throw new RunnerExecutionException('The execution unit \'' . $name . '\' was not found for \'' . $package . '=' .$version .'\''); - $process = match (strtolower($unit->ExecutionPolicy->Runner)) + Console::outDebug(sprintf('unit=%s', $unit->ExecutionPolicy->Name)); + Console::outDebug(sprintf('runner=%s', $unit->ExecutionPolicy->Runner)); + Console::outDebug(sprintf('file=%s', $unit->FilePointer)); + Console::outDebug(sprintf('pass_thru_args=%s', json_encode($args, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE))); + + // Handle the arguments + if($unit->ExecutionPolicy->Execute->Options !== null && count($unit->ExecutionPolicy->Execute->Options) > 0) { - Runners::php => PhpRunner::prepareProcess($unit), - default => throw new UnsupportedRunnerException('The runner \'' . $unit->ExecutionPolicy->Runner . '\' is not supported'), - }; + $args = array_merge($args, $unit->ExecutionPolicy->Execute->Options); + + foreach($unit->ExecutionPolicy->Execute->Options as $option) + { + $args[] = ConstantCompiler::compileRuntimeConstants($option); + } + } + + $process = new Process(array_merge( + [PathFinder::findRunner(strtolower($unit->ExecutionPolicy->Runner)), $unit->FilePointer], $args) + ); if($unit->ExecutionPolicy->Execute->WorkingDirectory !== null) - $process->setWorkingDirectory($unit->ExecutionPolicy->Execute->WorkingDirectory); + { + $process->setWorkingDirectory(ConstantCompiler::compileRuntimeConstants($unit->ExecutionPolicy->Execute->WorkingDirectory)); + } + if($unit->ExecutionPolicy->Execute->Timeout !== null) + { $process->setTimeout((float)$unit->ExecutionPolicy->Execute->Timeout); + } if($unit->ExecutionPolicy->Execute->Silent) { @@ -296,12 +409,20 @@ $process->enableOutput(); } + Console::outDebug(sprintf('working_directory=%s', $process->getWorkingDirectory())); + Console::outDebug(sprintf('timeout=%s', ($process->getTimeout() ?? 0))); + Console::outDebug(sprintf('silent=%s', ($unit->ExecutionPolicy->Execute->Silent ? 'true' : 'false'))); + Console::outDebug(sprintf('tty=%s', ($unit->ExecutionPolicy->Execute->Tty ? 'true' : 'false'))); + Console::outDebug(sprintf('options=%s', implode(' ', $args))); + Console::outDebug(sprintf('cmd=%s', $process->getCommandLine())); + try { if($unit->ExecutionPolicy->Message !== null) Console::out($unit->ExecutionPolicy->Message); - $process->run(function ($type, $buffer) { + $process->run(function ($type, $buffer) + { Console::out($buffer); }); @@ -313,6 +434,8 @@ $this->handleExit($package, $version, $unit->ExecutionPolicy->ExitHandlers->Error); } + Console::outDebug(sprintf('exit_code=%s', $process->getExitCode())); + if($unit->ExecutionPolicy->ExitHandlers !== null) { if($process->isSuccessful() && $unit->ExecutionPolicy->ExitHandlers->Success !== null) @@ -330,6 +453,8 @@ $this->handleExit($package, $version, $unit->ExecutionPolicy->ExitHandlers->Error, $process); } } + + return $process->getExitCode(); } /** @@ -339,12 +464,10 @@ * @param string $unit_name * @return void * @throws AccessDeniedException - * @throws ExecutionUnitNotFoundException * @throws FileNotFoundException * @throws IOException * @throws NoAvailableUnitsException * @throws RunnerExecutionException - * @throws UnsupportedRunnerException */ public function temporaryExecute(Package $package, string $unit_name): void { @@ -391,12 +514,10 @@ * @param Process|null $process * @return bool * @throws AccessDeniedException - * @throws ExecutionUnitNotFoundException * @throws FileNotFoundException * @throws IOException * @throws NoAvailableUnitsException * @throws RunnerExecutionException - * @throws UnsupportedRunnerException */ public function handleExit(string $package, string $version, ExitHandle $exitHandle, ?Process $process=null): bool { @@ -410,11 +531,13 @@ } elseif($exitHandle->EndProcess) { + Console::outDebug(sprintf('exit_code=%s', $process->getExitCode())); exit($exitHandle->ExitCode); } if($exitHandle->Run !== null) { + Console::outVerbose('Running unit \'' . $exitHandle->Run . '\''); $this->executeUnit($package, $version, $exitHandle->Run); } diff --git a/src/ncc/Managers/PackageLockManager.php b/src/ncc/Managers/PackageLockManager.php index 56d7daf..46134d6 100644 --- a/src/ncc/Managers/PackageLockManager.php +++ b/src/ncc/Managers/PackageLockManager.php @@ -1,7 +1,26 @@ sync(); + } + catch(Exception $e) + { + throw new PackageLockException('Failed to synchronize symlinks', $e); + } } /** diff --git a/src/ncc/Managers/PackageManager.php b/src/ncc/Managers/PackageManager.php index f22fafd..fb2cde0 100644 --- a/src/ncc/Managers/PackageManager.php +++ b/src/ncc/Managers/PackageManager.php @@ -1,4 +1,24 @@ getPackageVersion($package->Assembly->Package, $package->Assembly->Version) !== null) - throw new PackageAlreadyInstalledException('The package ' . $package->Assembly->Package . '=' . $package->Assembly->Version . ' is already installed'); - $extension = $package->Header->CompilerExtension->Extension; $installation_paths = new InstallationPaths($this->PackagesPath . DIRECTORY_SEPARATOR . $package->Assembly->Package . '=' . $package->Assembly->Version); - $installer = match ($extension) { + + $installer = match ($extension) + { CompilerExtensions::PHP => new PhpInstaller($package), default => throw new UnsupportedCompilerExtensionException('The compiler extension \'' . $extension . '\' is not supported'), }; + + if($this->getPackageVersion($package->Assembly->Package, $package->Assembly->Version) !== null) + { + if(in_array(InstallPackageOptions::Reinstall, $options)) + { + if($this->getPackageLockManager()->getPackageLock()->packageExists( + $package->Assembly->Package, $package->Assembly->Version + )) + { + $this->getPackageLockManager()->getPackageLock()->removePackageVersion( + $package->Assembly->Package, $package->Assembly->Version + ); + } + } + else + { + throw new PackageAlreadyInstalledException('The package ' . $package->Assembly->Package . '=' . $package->Assembly->Version . ' is already installed'); + } + } + $execution_pointer_manager = new ExecutionPointerManager(); PackageCompiler::compilePackageConstants($package, [ ConstantReferences::Install => $installation_paths ]); // Process all the required dependencies before installing the package - if($package->Dependencies !== null && count($package->Dependencies) > 0) + if($package->Dependencies !== null && count($package->Dependencies) > 0 && !in_array(InstallPackageOptions::SkipDependencies, $options)) { foreach($package->Dependencies as $dependency) { - $this->processDependency($dependency, $package, $package_path); + if(in_array(InstallPackageOptions::Reinstall, $options)) + { + // Uninstall the dependency if the option Reinstall is passed on + if($this->getPackageLockManager()->getPackageLock()->packageExists($dependency->Name, $dependency->Version)) + { + if($dependency->Version == null) + { + $this->uninstallPackage($dependency->Name); + } + else + { + $this->uninstallPackageVersion($dependency->Name, $dependency->Version); + } + } + } + + $this->processDependency($dependency, $package, $package_path, $entry); } } @@ -256,14 +330,15 @@ } // Install execution units - // TODO: Implement symlink support - if(count($package->ExecutionUnits) > 0) + if($package->ExecutionUnits !== null && count($package->ExecutionUnits) > 0) { $execution_pointer_manager = new ExecutionPointerManager(); $unit_paths = []; + /** @var Package\ExecutionUnit $executionUnit */ foreach($package->ExecutionUnits as $executionUnit) { + Console::outDebug(sprintf('processing execution unit %s', $executionUnit->ExecutionPolicy->Name)); $execution_pointer_manager->addUnit($package->Assembly->Package, $package->Assembly->Version, $executionUnit); $current_steps += 1; Console::inlineProgressBar($current_steps, $steps); @@ -272,6 +347,16 @@ IO::fwrite($installation_paths->getDataPath() . DIRECTORY_SEPARATOR . 'exec', ZiProto::encode($unit_paths)); } + // After execution units are installed, create a symlink if needed + if(isset($package->Header->Options['create_symlink']) && $package->Header->Options['create_symlink']) + { + if($package->MainExecutionPolicy === null) + throw new InstallationException('Cannot create symlink, no main execution policy is defined'); + + $SymlinkManager = new SymlinkManager(); + $SymlinkManager->add($package->Assembly->Package, $package->MainExecutionPolicy); + } + // Execute the post-installation stage after the installation is complete try { @@ -302,32 +387,227 @@ } } + if($package->Header->UpdateSource !== null && $package->Header->UpdateSource->Repository !== null) + { + $sources_manager = new RemoteSourcesManager(); + if($sources_manager->getRemoteSource($package->Header->UpdateSource->Repository->Name) === null) + { + Console::outVerbose('Adding remote source ' . $package->Header->UpdateSource->Repository->Name); + $defined_remote_source = new DefinedRemoteSource(); + $defined_remote_source->Name = $package->Header->UpdateSource->Repository->Name; + $defined_remote_source->Host = $package->Header->UpdateSource->Repository->Host; + $defined_remote_source->Type = $package->Header->UpdateSource->Repository->Type; + $defined_remote_source->SSL = $package->Header->UpdateSource->Repository->SSL; + + $sources_manager->addRemoteSource($defined_remote_source); + } + } + $this->getPackageLockManager()->getPackageLock()->addPackage($package, $installation_paths->getInstallationPath()); $this->getPackageLockManager()->save(); return $package->Assembly->Package; } + /** + * @param string $source + * @param Entry|null $entry + * @return string + * @throws InstallationException + * @throws NotImplementedException + * @throws PackageFetchException + */ + public function fetchFromSource(string $source, ?Entry $entry=null): string + { + $input = new RemotePackageInput($source); + + if($input->Source == null) + throw new PackageFetchException('No source specified'); + if($input->Package == null) + throw new PackageFetchException('No package specified'); + if($input->Version == null) + $input->Version = Versions::Latest; + + Console::outVerbose('Fetching package ' . $input->Package . ' from ' . $input->Source . ' (' . $input->Version . ')'); + + $remote_source_type = Resolver::detectRemoteSourceType($input->Source); + if($remote_source_type == RemoteSourceType::Builtin) + { + Console::outDebug('using builtin source ' . $input->Source); + switch($input->Source) + { + case 'composer': + try + { + return ComposerSourceBuiltin::fetch($input); + } + catch(Exception $e) + { + throw new PackageFetchException('Cannot fetch package from composer source, ' . $e->getMessage(), $e); + } + + default: + throw new NotImplementedException('Builtin source type ' . $input->Source . ' is not implemented'); + } + } + + if($remote_source_type == RemoteSourceType::Defined) + { + Console::outDebug('using defined source ' . $input->Source); + $remote_source_manager = new RemoteSourcesManager(); + $source = $remote_source_manager->getRemoteSource($input->Source); + if($source == null) + throw new InstallationException('Remote source ' . $input->Source . ' is not defined'); + + $repositoryQueryResults = Functions::getRepositoryQueryResults($input, $source, $entry); + $exceptions = []; + + if($repositoryQueryResults->Files->ZipballUrl !== null) + { + try + { + Console::outDebug(sprintf('fetching package %s from %s', $input->Package, $repositoryQueryResults->Files->ZipballUrl)); + $archive = Functions::downloadGitServiceFile($repositoryQueryResults->Files->ZipballUrl, $entry); + return PackageCompiler::tryCompile(Functions::extractArchive($archive), $repositoryQueryResults->Version); + } + catch(Throwable $e) + { + Console::outDebug('cannot fetch package from zipball url, ' . $e->getMessage()); + $exceptions[] = $e; + } + } + + if($repositoryQueryResults->Files->TarballUrl !== null) + { + try + { + Console::outDebug(sprintf('fetching package %s from %s', $input->Package, $repositoryQueryResults->Files->TarballUrl)); + $archive = Functions::downloadGitServiceFile($repositoryQueryResults->Files->TarballUrl, $entry); + return PackageCompiler::tryCompile(Functions::extractArchive($archive), $repositoryQueryResults->Version); + } + catch(Exception $e) + { + Console::outDebug('cannot fetch package from tarball url, ' . $e->getMessage()); + $exceptions[] = $e; + } + } + + if($repositoryQueryResults->Files->PackageUrl !== null) + { + try + { + Console::outDebug(sprintf('fetching package %s from %s', $input->Package, $repositoryQueryResults->Files->PackageUrl)); + return Functions::downloadGitServiceFile($repositoryQueryResults->Files->PackageUrl, $entry); + } + catch(Exception $e) + { + Console::outDebug('cannot fetch package from package url, ' . $e->getMessage()); + $exceptions[] = $e; + } + } + + if($repositoryQueryResults->Files->GitHttpUrl !== null || $repositoryQueryResults->Files->GitSshUrl !== null) + { + try + { + Console::outDebug(sprintf('fetching package %s from %s', $input->Package, $repositoryQueryResults->Files->GitHttpUrl ?? $repositoryQueryResults->Files->GitSshUrl)); + $git_repository = GitClient::cloneRepository($repositoryQueryResults->Files->GitHttpUrl ?? $repositoryQueryResults->Files->GitSshUrl); + + foreach(GitClient::getTags($git_repository) as $tag) + { + if(VersionComparator::compareVersion($tag, $repositoryQueryResults->Version) === 0) + { + GitClient::checkout($git_repository, $tag); + return PackageCompiler::tryCompile($git_repository, $repositoryQueryResults->Version); + } + } + + Console::outDebug('cannot fetch package from git repository, no matching tag found'); + } + catch(Exception $e) + { + Console::outDebug('cannot fetch package from git repository, ' . $e->getMessage()); + $exceptions[] = $e; + } + } + + // Recursively create an exception with the previous exceptions as the previous exception + $exception = null; + + if(count($exceptions) > 0) + { + foreach($exceptions as $e) + { + if($exception == null) + { + $exception = new PackageFetchException($e->getMessage(), $e); + } + else + { + if($e->getMessage() == $exception->getMessage()) + continue; + + $exception = new PackageFetchException($e->getMessage(), $exception); + } + } + } + else + { + $exception = new PackageFetchException('Cannot fetch package from remote source, no assets found'); + } + + throw $exception; + } + + throw new PackageFetchException(sprintf('Unknown remote source type %s', $remote_source_type)); + } + + /** + * Installs a package from a source syntax (vendor/package=version@source) + * + * @param string $source + * @param Entry|null $entry + * @return string + * @throws InstallationException + */ + public function installFromSource(string $source, ?Entry $entry): string + { + try + { + Console::outVerbose(sprintf('Installing package from source %s', $source)); + $package = $this->fetchFromSource($source, $entry); + return $this->install($package, $entry); + } + catch(Exception $e) + { + throw new InstallationException('Cannot install package from source, ' . $e->getMessage(), $e); + } + } + /** * @param Dependency $dependency * @param Package $package * @param string $package_path + * @param Entry|null $entry * @return void * @throws AccessDeniedException * @throws FileNotFoundException * @throws IOException * @throws InstallationException + * @throws InvalidPackageNameException + * @throws InvalidScopeException * @throws MissingDependencyException * @throws NotImplementedException * @throws PackageAlreadyInstalledException * @throws PackageLockException * @throws PackageNotFoundException * @throws PackageParsingException + * @throws SymlinkException * @throws UnsupportedCompilerExtensionException - * @throws UnsupportedRunnerException * @throws VersionNotFoundException + * @throws RunnerExecutionException */ - private function processDependency(Dependency $dependency, Package $package, string $package_path): void + private function processDependency(Dependency $dependency, Package $package, string $package_path, ?Entry $entry=null): void { Console::outVerbose('processing dependency ' . $dependency->Name . ' (' . $dependency->Version . ')'); $dependent_package = $this->getPackage($dependency->Name); @@ -335,39 +615,45 @@ if ($dependent_package !== null && $dependency->Version !== null && Validate::version($dependency->Version)) { + Console::outDebug('dependency has version constraint, checking if package is installed'); $dependent_version = $this->getPackageVersion($dependency->Name, $dependency->Version); if ($dependent_version !== null) $dependency_met = true; } elseif ($dependent_package !== null && $dependency->Version == null) { + Console::outDebug(sprintf('dependency %s has no version specified, assuming dependency is met', $dependency->Name)); $dependency_met = true; } - if ($dependency->SourceType !== null) + Console::outDebug('dependency met: ' . ($dependency_met ? 'true' : 'false')); + + if ($dependency->SourceType !== null && !$dependency_met) { - if(!$dependency_met) + Console::outVerbose(sprintf('Installing dependency %s=%s for %s=%s', $dependency->Name, $dependency->Version, $package->Assembly->Package, $package->Assembly->Version)); + switch ($dependency->SourceType) { - Console::outVerbose(sprintf('Installing dependency %s=%s for %s=%s', $dependency->Name, $dependency->Version, $package->Assembly->Package, $package->Assembly->Version)); - switch ($dependency->SourceType) - { - case DependencySourceType::Local: - Console::outDebug('installing from local source ' . $dependency->Source); - $basedir = dirname($package_path); - if (!file_exists($basedir . DIRECTORY_SEPARATOR . $dependency->Source)) - throw new FileNotFoundException($basedir . DIRECTORY_SEPARATOR . $dependency->Source); - $this->install($basedir . DIRECTORY_SEPARATOR . $dependency->Source); - break; + case DependencySourceType::Local: + Console::outDebug('installing from local source ' . $dependency->Source); + $basedir = dirname($package_path); + if (!file_exists($basedir . DIRECTORY_SEPARATOR . $dependency->Source)) + throw new FileNotFoundException($basedir . DIRECTORY_SEPARATOR . $dependency->Source); + $this->install($basedir . DIRECTORY_SEPARATOR . $dependency->Source); + break; - case DependencySourceType::StaticLinking: - throw new PackageNotFoundException('Static linking not possible, package ' . $dependency->Name . ' is not installed'); + case DependencySourceType::StaticLinking: + throw new PackageNotFoundException('Static linking not possible, package ' . $dependency->Name . ' is not installed'); - default: - throw new NotImplementedException('Dependency source type ' . $dependency->SourceType . ' is not implemented'); - } + case DependencySourceType::RemoteSource: + Console::outDebug('installing from remote source ' . $dependency->Source); + $this->installFromSource($dependency->Source, $entry); + break; + + default: + throw new NotImplementedException('Dependency source type ' . $dependency->SourceType . ' is not implemented'); } } - else + elseif(!$dependency_met) { throw new MissingDependencyException(sprintf('The dependency %s=%s for %s=%s is not met', $dependency->Name, $dependency->Version, $package->Assembly->Package, $package->Assembly->Version)); } @@ -379,10 +665,10 @@ * @param string $package * @return PackageEntry|null * @throws PackageLockException - * @throws PackageLockException */ public function getPackage(string $package): ?PackageEntry { + Console::outDebug('getting package ' . $package); return $this->getPackageLockManager()->getPackageLock()->getPackage($package); } @@ -397,6 +683,7 @@ */ public function getPackageVersion(string $package, string $version): ?VersionEntry { + Console::outDebug('getting package version ' . $package . '=' . $version); return $this->getPackage($package)?->getVersion($version); } @@ -407,9 +694,11 @@ * @return VersionEntry|null * @throws VersionNotFoundException * @throws PackageLockException + * @noinspection PhpUnused */ public function getLatestVersion(string $package): ?VersionEntry { + Console::outDebug('getting latest version of package ' . $package); return $this->getPackage($package)?->getVersion($this->getPackage($package)?->getLatestVersion()); } @@ -442,7 +731,15 @@ $exploded = explode('=', $package); try { - foreach ($this->getPackage($exploded[0])?->getVersion($exploded[1])?->Dependencies as $dependency) + $package = $this->getPackage($exploded[0]); + if($package == null) + throw new PackageNotFoundException('Package ' . $exploded[0] . ' not found'); + + $version = $package->getVersion($exploded[1]); + if($version == null) + throw new VersionNotFoundException('Version ' . $exploded[1] . ' not found for package ' . $exploded[0]); + + foreach ($version->Dependencies as $dependency) { if(!in_array($dependency->PackageName . '=' . $dependency->Version, $tree)) $packages[] = $dependency->PackageName . '=' . $dependency->Version; @@ -481,14 +778,21 @@ try { $version_entry = $this->getPackageVersion($package_e[0], $package_e[1]); - $tree[$package] = null; - if($version_entry->Dependencies !== null && count($version_entry->Dependencies) > 0) + if($version_entry == null) { - $tree[$package] = []; - foreach($version_entry->Dependencies as $dependency) + Console::outWarning('Version ' . $package_e[1] . ' of package ' . $package_e[0] . ' not found'); + } + else + { + $tree[$package] = null; + if($version_entry->Dependencies !== null && count($version_entry->Dependencies) > 0) { - $dependency_name = sprintf('%s=%s', $dependency->PackageName, $dependency->Version); - $tree[$package] = $this->getPackageTree($tree[$package], $dependency_name); + $tree[$package] = []; + foreach($version_entry->Dependencies as $dependency) + { + $dependency_name = sprintf('%s=%s', $dependency->PackageName, $dependency->Version); + $tree[$package] = $this->getPackageTree($tree[$package], $dependency_name); + } } } } @@ -512,6 +816,7 @@ * @throws IOException * @throws PackageLockException * @throws PackageNotFoundException + * @throws SymlinkException * @throws VersionNotFoundException */ public function uninstallPackageVersion(string $package, string $version): void @@ -534,16 +839,26 @@ $scanner = new DirectoryScanner(); $filesystem = new Filesystem(); - /** @var SplFileInfo $item */ - /** @noinspection PhpRedundantOptionalArgumentInspection */ - foreach($scanner($version_entry->Location, true) as $item) + if($filesystem->exists($version_entry->Location)) { - if(is_file($item->getPath())) + Console::outVerbose(sprintf('Removing package files from %s', $version_entry->Location)); + + /** @var SplFileInfo $item */ + /** @noinspection PhpRedundantOptionalArgumentInspection */ + foreach($scanner($version_entry->Location, true) as $item) { - Console::outDebug(sprintf('deleting %s', $item->getPath())); - $filesystem->remove($item->getPath()); + if(is_file($item->getPath())) + { + Console::outDebug('removing file ' . $item->getPath()); + Console::outDebug(sprintf('deleting %s', $item->getPath())); + $filesystem->remove($item->getPath()); + } } } + else + { + Console::outWarning(sprintf('warning: package location %s does not exist', $version_entry->Location)); + } $filesystem->remove($version_entry->Location); @@ -558,6 +873,9 @@ Console::outDebug(sprintf('warning: removing execution unit %s failed', $executionUnit->ExecutionPolicy->Name)); } } + + $symlink_manager = new SymlinkManager(); + $symlink_manager->sync(); } /** @@ -582,6 +900,7 @@ foreach($package_entry->getVersions() as $version) { $version_entry = $package_entry->getVersion($version); + try { $this->uninstallPackageVersion($package, $version_entry->Version); @@ -600,6 +919,8 @@ */ private static function initData(Package $package, InstallationPaths $paths): void { + Console::outVerbose(sprintf('Initializing data for %s', $package->Assembly->Name)); + // Create data files $dependencies = []; foreach($package->Dependencies as $dependency) @@ -611,7 +932,7 @@ $paths->getDataPath() . DIRECTORY_SEPARATOR . 'assembly' => ZiProto::encode($package->Assembly->toArray(true)), $paths->getDataPath() . DIRECTORY_SEPARATOR . 'ext' => - ZiProto::encode($package->Header->CompilerExtension->toArray(true)), + ZiProto::encode($package->Header->CompilerExtension->toArray()), $paths->getDataPath() . DIRECTORY_SEPARATOR . 'const' => ZiProto::encode($package->Header->RuntimeConstants), $paths->getDataPath() . DIRECTORY_SEPARATOR . 'dependencies' => @@ -622,6 +943,7 @@ { try { + Console::outDebug(sprintf('generating data file %s', $file)); IO::fwrite($file, $data); } catch (IOException $e) diff --git a/src/ncc/Managers/ProjectManager.php b/src/ncc/Managers/ProjectManager.php index 286910b..9e28ba6 100644 --- a/src/ncc/Managers/ProjectManager.php +++ b/src/ncc/Managers/ProjectManager.php @@ -1,4 +1,24 @@ ProjectConfiguration->Build->DefineConstants['ASSEMBLY_UID'] = '%ASSEMBLY.UID%'; // Generate configurations - $DebugConfiguration = new ProjectConfiguration\BuildConfiguration(); + $DebugConfiguration = new ProjectConfiguration\Build\BuildConfiguration(); $DebugConfiguration->Name = 'debug'; $DebugConfiguration->OutputPath = 'build/debug'; $DebugConfiguration->DefineConstants["DEBUG"] = '1'; // Debugging constant if the program wishes to check for this $this->ProjectConfiguration->Build->Configurations[] = $DebugConfiguration; - $ReleaseConfiguration = new ProjectConfiguration\BuildConfiguration(); + $ReleaseConfiguration = new ProjectConfiguration\Build\BuildConfiguration(); $ReleaseConfiguration->Name = 'release'; $ReleaseConfiguration->OutputPath = 'build/release'; $ReleaseConfiguration->DefineConstants["DEBUG"] = '0'; // Debugging constant if the program wishes to check for this @@ -172,14 +191,10 @@ // Process options foreach($options as $option) { - switch($option) - { - case InitializeProjectOptions::CREATE_SOURCE_DIRECTORY: - if(!file_exists($this->ProjectConfiguration->Build->SourcePath)) - { - mkdir($this->ProjectConfiguration->Build->SourcePath); - } - break; + if ($option == InitializeProjectOptions::CREATE_SOURCE_DIRECTORY) { + if (!file_exists($this->ProjectConfiguration->Build->SourcePath)) { + mkdir($this->ProjectConfiguration->Build->SourcePath); + } } } } @@ -207,7 +222,7 @@ * @throws FileNotFoundException * @throws IOException */ - public function load() + public function load(): void { if(!file_exists($this->ProjectFilePath) && !is_file($this->ProjectFilePath)) throw new ProjectConfigurationNotFoundException('The project configuration file \'' . $this->ProjectFilePath . '\' was not found'); @@ -221,7 +236,7 @@ * @return void * @throws MalformedJsonException */ - public function save() + public function save(): void { if(!$this->projectLoaded()) return; @@ -274,7 +289,6 @@ * @throws BuildException * @throws PackagePreparationFailedException * @throws UnsupportedCompilerExtensionException - * @throws UnsupportedRunnerException */ public function build(string $build_configuration=BuildConfigurationValues::DefaultConfiguration): string { diff --git a/src/ncc/Managers/RemoteSourcesManager.php b/src/ncc/Managers/RemoteSourcesManager.php new file mode 100644 index 0000000..dc1f884 --- /dev/null +++ b/src/ncc/Managers/RemoteSourcesManager.php @@ -0,0 +1,171 @@ +DefinedSourcesPath = PathFinder::getRemoteSources(Scopes::System); + + $this->load(); + } + + /** + * Loads an existing remote sources file, or creates a new one if it doesn't exist + * + * @return void + */ + public function load(): void + { + $this->Sources = []; + + try + { + + if(file_exists($this->DefinedSourcesPath)) + { + $sources = ZiProto::decode(IO::fread($this->DefinedSourcesPath)); + $this->Sources = []; + foreach($sources as $source) + $this->Sources[] = DefinedRemoteSource::fromArray($source); + } + } + catch(Exception $e) + { + unset($e); + } + } + + /** + * Saves the remote sources file to disk + * + * @return void + * @throws IOException + */ + public function save(): void + { + $sources = []; + foreach($this->Sources as $source) + $sources[] = $source->toArray(true); + + IO::fwrite($this->DefinedSourcesPath, ZiProto::encode($sources)); + } + + /** + * Adds a new remote source to the list + * + * @param DefinedRemoteSource $source + * @return bool + */ + public function addRemoteSource(DefinedRemoteSource $source): bool + { + foreach($this->Sources as $existingSource) + { + if($existingSource->Name === $source->Name) + return false; + } + + $this->Sources[] = $source; + return true; + } + + /** + * Gets a remote source by its name + * + * @param string $name + * @return DefinedRemoteSource|null + */ + public function getRemoteSource(string $name): ?DefinedRemoteSource + { + foreach($this->Sources as $source) + { + if($source->Name === $name) + return $source; + } + + return null; + } + + /** + * Deletes an existing remote source + * + * @param string $name + * @return bool + */ + public function deleteRemoteSource(string $name): bool + { + foreach($this->Sources as $index => $source) + { + if($source->Name === $name) + { + unset($this->Sources[$index]); + return true; + } + } + + return false; + } + + /** + * Returns an array of all the defined remote sources + * + * @return DefinedRemoteSource[] + */ + public function getSources(): array + { + if($this->Sources == null) + $this->load(); + return $this->Sources; + } + } \ No newline at end of file diff --git a/src/ncc/Managers/SymlinkManager.php b/src/ncc/Managers/SymlinkManager.php new file mode 100644 index 0000000..ce3e2e1 --- /dev/null +++ b/src/ncc/Managers/SymlinkManager.php @@ -0,0 +1,355 @@ +SymlinkDictionaryPath = PathFinder::getSymlinkDictionary(Scopes::System); + $this->load(); + } + catch(Exception $e) + { + Console::outWarning(sprintf('failed to load symlink dictionary from %s', $this->SymlinkDictionaryPath)); + } + finally + { + if($this->SymlinkDictionary === null) + $this->SymlinkDictionary = []; + + unset($e); + } + } + + /** + * Loads the symlink dictionary from the file + * + * @return void + * @throws AccessDeniedException + * @throws SymlinkException + */ + public function load(): void + { + if($this->SymlinkDictionary !== null) + return; + + Console::outDebug(sprintf('loading symlink dictionary from %s', $this->SymlinkDictionaryPath)); + + if(!file_exists($this->SymlinkDictionaryPath)) + { + Console::outDebug('symlink dictionary does not exist, creating new dictionary'); + $this->SymlinkDictionary = []; + $this->save(false); + return; + } + + try + { + $this->SymlinkDictionary = []; + + foreach(ZiProto::decode(IO::fread($this->SymlinkDictionaryPath)) as $entry) + { + $this->SymlinkDictionary[] = SymlinkEntry::fromArray($entry); + } + } + catch(Exception $e) + { + $this->SymlinkDictionary = []; + + Console::outDebug('symlink dictionary is corrupted, creating new dictionary'); + $this->save(false); + } + finally + { + unset($e); + } + } + + /** + * Saves the symlink dictionary to the file + * + * @param bool $throw_exception + * @return void + * @throws AccessDeniedException + * @throws SymlinkException + */ + private function save(bool $throw_exception=true): void + { + if(Resolver::resolveScope() !== Scopes::System) + throw new AccessDeniedException('Insufficient Permissions to write to the system symlink dictionary'); + + Console::outDebug(sprintf('saving symlink dictionary to %s', $this->SymlinkDictionaryPath)); + + try + { + $dictionary = []; + foreach($this->SymlinkDictionary as $entry) + { + $dictionary[] = $entry->toArray(true); + } + + IO::fwrite($this->SymlinkDictionaryPath, ZiProto::encode($dictionary)); + } + catch(Exception $e) + { + if($throw_exception) + throw new SymlinkException(sprintf('failed to save symlink dictionary to %s', $this->SymlinkDictionaryPath), $e); + + Console::outWarning(sprintf('failed to save symlink dictionary to %s', $this->SymlinkDictionaryPath)); + } + finally + { + unset($e); + } + } + + /** + * @return string + */ + public function getSymlinkDictionaryPath(): string + { + return $this->SymlinkDictionaryPath; + } + + /** + * @return array + */ + public function getSymlinkDictionary(): array + { + return $this->SymlinkDictionary; + } + + /** + * Checks if a package is defined in the symlink dictionary + * + * @param string $package + * @return bool + */ + public function exists(string $package): bool + { + foreach($this->SymlinkDictionary as $entry) + { + if($entry->Package === $package) + return true; + } + + return false; + } + + /** + * Adds a new entry to the symlink dictionary + * + * @param string $package + * @param string $unit + * @return void + * @throws AccessDeniedException + * @throws SymlinkException + */ + public function add(string $package, string $unit='main'): void + { + if(Resolver::resolveScope() !== Scopes::System) + throw new AccessDeniedException('Insufficient Permissions to add to the system symlink dictionary'); + + if($this->exists($package)) + $this->remove($package); + + $entry = new SymlinkEntry(); + $entry->Package = $package; + $entry->ExecutionPolicyName = $unit; + + $this->SymlinkDictionary[] = $entry; + $this->save(); + } + + /** + * Removes an entry from the symlink dictionary + * + * @param string $package + * @return void + * @throws AccessDeniedException + * @throws SymlinkException + */ + public function remove(string $package): void + { + if(Resolver::resolveScope() !== Scopes::System) + throw new AccessDeniedException('Insufficient Permissions to remove from the system symlink dictionary'); + + if(!$this->exists($package)) + return; + + foreach($this->SymlinkDictionary as $key => $entry) + { + if($entry->Package === $package) + { + if($entry->Registered) + { + $filesystem = new Filesystem(); + + $symlink_name = explode('.', $entry->Package)[count(explode('.', $entry->Package)) - 1]; + $symlink = self::$BinPath . DIRECTORY_SEPARATOR . $symlink_name; + + if($filesystem->exists($symlink)) + $filesystem->remove($symlink); + } + + unset($this->SymlinkDictionary[$key]); + $this->save(); + return; + } + } + + throw new SymlinkException(sprintf('failed to remove package %s from the symlink dictionary', $package)); + } + + /** + * Sets the package as registered + * + * @param string $package + * @return void + * @throws AccessDeniedException + * @throws SymlinkException + */ + private function setAsRegistered(string $package): void + { + foreach($this->SymlinkDictionary as $key => $entry) + { + if($entry->Package === $package) + { + $entry->Registered = true; + $this->SymlinkDictionary[$key] = $entry; + $this->save(); + return; + } + } + } + + /** + * Syncs the symlink dictionary with the filesystem + * + * @return void + * @throws AccessDeniedException + * @throws SymlinkException + */ + public function sync(): void + { + if(Resolver::resolveScope() !== Scopes::System) + throw new AccessDeniedException('Insufficient Permissions to sync the system symlink dictionary'); + + $filesystem = new Filesystem(); + $execution_pointer_manager = new ExecutionPointerManager(); + $package_lock_manager = new PackageLockManager(); + + foreach($this->SymlinkDictionary as $entry) + { + if($entry->Registered) + continue; + + $symlink_name = explode('.', $entry->Package)[count(explode('.', $entry->Package)) - 1]; + $symlink = self::$BinPath . DIRECTORY_SEPARATOR . $symlink_name; + + if($filesystem->exists($symlink)) + { + Console::outWarning(sprintf('Symlink %s already exists, skipping', $symlink)); + continue; + } + + try + { + $package_entry = $package_lock_manager->getPackageLock()->getPackage($entry->Package); + + if($package_entry == null) + { + Console::outWarning(sprintf('Package %s is not installed, skipping', $entry->Package)); + continue; + } + + $latest_version = $package_entry->getLatestVersion(); + + } + catch(Exception $e) + { + $filesystem->remove($symlink); + Console::outWarning(sprintf('Failed to get package %s, skipping', $entry->Package)); + continue; + } + + try + { + $entry_point_path = $execution_pointer_manager->getEntryPointPath($entry->Package, $latest_version, $entry->ExecutionPolicyName); + $filesystem->symlink($entry_point_path, $symlink); + } + catch(Exception $e) + { + $filesystem->remove($symlink); + Console::outWarning(sprintf('Failed to create symlink %s, skipping', $symlink)); + continue; + } + finally + { + unset($e); + } + + $this->setAsRegistered($entry->Package); + + } + } + } \ No newline at end of file diff --git a/src/ncc/Objects/CliHelpSection.php b/src/ncc/Objects/CliHelpSection.php index 90c878e..c8913a5 100644 --- a/src/ncc/Objects/CliHelpSection.php +++ b/src/ncc/Objects/CliHelpSection.php @@ -1,4 +1,24 @@ Readonly == true) + if($this->Readonly) { throw new ConstantReadonlyException('Cannot set value to the constant \'' . $this->getFullName() . '\', constant is readonly'); } diff --git a/src/ncc/Objects/DefinedRemoteSource.php b/src/ncc/Objects/DefinedRemoteSource.php new file mode 100644 index 0000000..de63655 --- /dev/null +++ b/src/ncc/Objects/DefinedRemoteSource.php @@ -0,0 +1,95 @@ + $this->Name, + ($bytecode ? Functions::cbc('type') : 'type') => $this->Type, + ($bytecode ? Functions::cbc('host') : 'host') => $this->Host, + ($bytecode ? Functions::cbc('ssl') : 'ssl') => $this->SSL + ]; + } + + /** + * Constructs object from an array representation. + * + * @param array $data + * @return static + */ + public static function fromArray(array $data): self + { + $definedRemoteSource = new self(); + + $definedRemoteSource->Name = Functions::array_bc($data, 'name'); + $definedRemoteSource->Type = Functions::array_bc($data, 'type'); + $definedRemoteSource->Host = Functions::array_bc($data, 'host'); + $definedRemoteSource->SSL = Functions::array_bc($data, 'ssl'); + + return $definedRemoteSource; + } + } \ No newline at end of file diff --git a/src/ncc/Objects/ExecutionPointers.php b/src/ncc/Objects/ExecutionPointers.php index 9181c03..c1f6a3b 100644 --- a/src/ncc/Objects/ExecutionPointers.php +++ b/src/ncc/Objects/ExecutionPointers.php @@ -1,4 +1,24 @@ Pointers as $pointer) { if($pointer->ExecutionPolicy->Name == $name) @@ -148,7 +172,11 @@ { $pointers[] = $pointer->toArray($bytecode); } - return $pointers; + return [ + ($bytecode ? Functions::cbc('package') : 'package') => $this->Package, + ($bytecode ? Functions::cbc('version') : 'version') => $this->Version, + ($bytecode ? Functions::cbc('pointers') : 'pointers') => $pointers + ]; } /** @@ -161,9 +189,18 @@ { $object = new self(); - foreach($data as $datum) + $object->Version = Functions::array_bc($data, 'version'); + $object->Package = Functions::array_bc($data, 'package'); + $object->Pointers = Functions::array_bc($data, 'pointers'); + + if($object->Pointers !== null) { - $object->Pointers[] = ExecutionPointer::fromArray($datum); + $pointers = []; + foreach($object->Pointers as $pointer) + { + $pointers[] = ExecutionPointer::fromArray($pointer); + } + $object->Pointers = $pointers; } return $object; diff --git a/src/ncc/Objects/ExecutionPointers/ExecutionPointer.php b/src/ncc/Objects/ExecutionPointers/ExecutionPointer.php index 9a5747e..e736eca 100644 --- a/src/ncc/Objects/ExecutionPointers/ExecutionPointer.php +++ b/src/ncc/Objects/ExecutionPointers/ExecutionPointer.php @@ -1,4 +1,24 @@ ExecutionPolicy = Functions::array_bc($data, 'execution_policy'); $object->FilePointer = Functions::array_bc($data, 'file_pointer'); + if($object->ExecutionPolicy !== null) + $object->ExecutionPolicy = ExecutionPolicy::fromArray($object->ExecutionPolicy); + return $object; } } \ No newline at end of file diff --git a/src/ncc/Objects/HttpRequest.php b/src/ncc/Objects/HttpRequest.php new file mode 100644 index 0000000..138f76e --- /dev/null +++ b/src/ncc/Objects/HttpRequest.php @@ -0,0 +1,128 @@ +Type = HttpRequestType::GET; + $this->Body = null; + $this->Headers = [ + 'User-Agent: ncc/1.0' + ]; + $this->Options = []; + } + + /** + * Returns an array representation of the object. + * + * @return array + */ + public function toArray(): array + { + return [ + 'type' => $this->Type, + 'url' => $this->Url, + 'headers' => $this->Headers, + 'body' => $this->Body, + 'authentication' => $this->Authentication, + 'options' => $this->Options + ]; + } + + /** + * Returns the hash of the object. + * (This is used for caching) + * + * @return string + */ + public function requestHash(): string + { + return hash('sha1', json_encode($this->toArray())); + } + + /** + * Constructs a new HttpRequest object from an array representation. + * + * @param array $data + * @return static + */ + public static function fromArray(array $data): self + { + $request = new self(); + $request->Type = $data['type']; + $request->Url = $data['url']; + $request->Headers = $data['headers']; + $request->Body = $data['body']; + $request->Authentication = $data['authentication']; + $request->Options = $data['options']; + return $request; + } + } \ No newline at end of file diff --git a/src/ncc/Objects/HttpResponse.php b/src/ncc/Objects/HttpResponse.php new file mode 100644 index 0000000..d412b99 --- /dev/null +++ b/src/ncc/Objects/HttpResponse.php @@ -0,0 +1,70 @@ +StatusCode = 0; + $this->Headers = []; + $this->Body = ''; + } + + /** + * Returns an array representation of the object. + * + * @return array + */ + public function toArray(): array + { + return [ + 'status_code' => $this->StatusCode, + 'headers' => $this->Headers, + 'body' => $this->Body + ]; + } + } \ No newline at end of file diff --git a/src/ncc/Objects/HttpResponseCache.php b/src/ncc/Objects/HttpResponseCache.php new file mode 100644 index 0000000..5ac68a0 --- /dev/null +++ b/src/ncc/Objects/HttpResponseCache.php @@ -0,0 +1,74 @@ +httpResponse = $httpResponse; + $this->ttl = $ttl; + } + + /** + * Returns the cached response + * + * @return HttpResponse + */ + public function getHttpResponse(): HttpResponse + { + return $this->httpResponse; + } + + /** + * Returns the Unix Timestamp of when the cache becomes invalid + * + * @return int + */ + public function getTtl(): int + { + return $this->ttl; + } + } \ No newline at end of file diff --git a/src/ncc/Objects/InstallationPaths.php b/src/ncc/Objects/InstallationPaths.php index d7c745a..b39225e 100644 --- a/src/ncc/Objects/InstallationPaths.php +++ b/src/ncc/Objects/InstallationPaths.php @@ -1,4 +1,24 @@ Resources = []; } + /** + * Adds a dependency to the package + * + * @param Dependency $dependency + * @return void + */ + public function addDependency(Dependency $dependency): void + { + foreach($this->Dependencies as $dep) + { + if($dep->Name == $dependency->Name) + { + $this->removeDependency($dep->Name); + break; + } + } + + $this->Dependencies[] = $dependency; + } + + /** + * Removes a dependency from the build + * + * @param string $name + * @return void + */ + private function removeDependency(string $name): void + { + foreach($this->Dependencies as $key => $dep) + { + if($dep->Name == $name) + { + unset($this->Dependencies[$key]); + return; + } + } + } + /** * Validates the package object and returns True if the package contains the correct information * diff --git a/src/ncc/Objects/Package/Component.php b/src/ncc/Objects/Package/Component.php index b48708b..8a464e0 100644 --- a/src/ncc/Objects/Package/Component.php +++ b/src/ncc/Objects/Package/Component.php @@ -1,4 +1,24 @@ ExecutionPolicy = Functions::array_bc($data, 'execution_policy'); $object->Data = Functions::array_bc($data, 'data'); + if($object->ExecutionPolicy !== null) + $object->ExecutionPolicy = ExecutionPolicy::fromArray($object->ExecutionPolicy); + return $object; } diff --git a/src/ncc/Objects/Package/Header.php b/src/ncc/Objects/Package/Header.php index 34804de..b53779d 100644 --- a/src/ncc/Objects/Package/Header.php +++ b/src/ncc/Objects/Package/Header.php @@ -1,10 +1,31 @@ $this->CompilerExtension->toArray($bytecode), + ($bytecode ? Functions::cbc('compiler_extension') : 'compiler_extension') => $this->CompilerExtension->toArray(), ($bytecode ? Functions::cbc('runtime_constants') : 'runtime_constants') => $this->RuntimeConstants, ($bytecode ? Functions::cbc('compiler_version') : 'compiler_version') => $this->CompilerVersion, + ($bytecode ? Functions::cbc('update_source') : 'update_source') => ($this->UpdateSource?->toArray($bytecode) ?? null), ($bytecode ? Functions::cbc('options') : 'options') => $this->Options, ]; } @@ -76,10 +105,13 @@ $object->CompilerExtension = Functions::array_bc($data, 'compiler_extension'); $object->RuntimeConstants = Functions::array_bc($data, 'runtime_constants'); $object->CompilerVersion = Functions::array_bc($data, 'compiler_version'); + $object->UpdateSource = Functions::array_bc($data, 'update_source'); $object->Options = Functions::array_bc($data, 'options'); if($object->CompilerExtension !== null) $object->CompilerExtension = Compiler::fromArray($object->CompilerExtension); + if($object->UpdateSource !== null) + $object->UpdateSource = UpdateSource::fromArray($object->UpdateSource); return $object; } diff --git a/src/ncc/Objects/Package/Installer.php b/src/ncc/Objects/Package/Installer.php index c368aac..bf949d0 100644 --- a/src/ncc/Objects/Package/Installer.php +++ b/src/ncc/Objects/Package/Installer.php @@ -1,4 +1,24 @@ Assembly->Package} to package lock file"); + if(!isset($this->Packages[$package->Assembly->Package])) { $package_entry = new PackageEntry(); $package_entry->addVersion($package, $install_path, true); $package_entry->Name = $package->Assembly->Package; + $package_entry->UpdateSource = $package->Header->UpdateSource; + $package_entry->getDataPath(); $this->Packages[$package->Assembly->Package] = $package_entry; $this->update(); return; } - $this->Packages[$package->Assembly->Package]->addVersion($package, true); + $this->Packages[$package->Assembly->Package]->UpdateSource = $package->Header->UpdateSource; + $this->Packages[$package->Assembly->Package]->addVersion($package, $install_path, true); + $this->Packages[$package->Assembly->Package]->getDataPath(); $this->update(); } @@ -82,6 +114,8 @@ */ public function removePackageVersion(string $package, string $version): bool { + Console::outVerbose(sprintf('Removing package %s version %s from package lock file', $package, $version)); + if(isset($this->Packages[$package])) { $r = $this->Packages[$package]->removeVersion($version); @@ -104,12 +138,15 @@ * * @param string $package * @return bool + * @noinspection PhpUnused */ public function removePackage(string $package): bool { + Console::outVerbose(sprintf('Removing package %s from package lock file', $package)); if(isset($this->Packages[$package])) { unset($this->Packages[$package]); + $this->update(); return true; } @@ -124,6 +161,8 @@ */ public function getPackage(string $package): ?PackageEntry { + Console::outDebug(sprintf('getting package %s from package lock file', $package)); + if(isset($this->Packages[$package])) { return $this->Packages[$package]; @@ -132,6 +171,40 @@ return null; } + /** + * Determines if the requested package exists in the package lock + * + * @param string $package + * @param string|null $version + * @return bool + */ + public function packageExists(string $package, ?string $version=null): bool + { + $package_entry = $this->getPackage($package); + if($package_entry == null) + return false; + + if($version !== null) + { + try + { + $version_entry = $package_entry->getVersion($version); + } + catch (VersionNotFoundException $e) + { + unset($e); + return false; + } + + if($version_entry == null) + { + return false; + } + } + + return true; + } + /** * Returns an array of all packages and their installed versions * diff --git a/src/ncc/Objects/PackageLock/DependencyEntry.php b/src/ncc/Objects/PackageLock/DependencyEntry.php index 82be14e..bbd1db2 100644 --- a/src/ncc/Objects/PackageLock/DependencyEntry.php +++ b/src/ncc/Objects/PackageLock/DependencyEntry.php @@ -1,4 +1,24 @@ LatestVersion; + foreach($this->Versions as $versionEntry) { if($versionEntry->Version == $version) @@ -128,6 +166,9 @@ $version->MainExecutionPolicy = $package->MainExecutionPolicy; $version->Location = $install_path; + foreach($version->ExecutionUnits as $unit) + $unit->Data = null; + foreach($package->Dependencies as $dependency) { $version->Dependencies[] = new DependencyEntry($dependency); @@ -186,6 +227,24 @@ return $r; } + /** + * @return string + * @throws InvalidPackageNameException + * @throws InvalidScopeException + */ + public function getDataPath(): string + { + $path = PathFinder::getPackageDataPath($this->Name); + + if(!file_exists($path) && Resolver::resolveScope() == Scopes::System) + { + $filesystem = new Filesystem(); + $filesystem->mkdir($path); + } + + return $path; + } + /** * Returns an array representation of the object * @@ -204,6 +263,7 @@ ($bytecode ? Functions::cbc('name') : 'name') => $this->Name, ($bytecode ? Functions::cbc('latest_version') : 'latest_version') => $this->LatestVersion, ($bytecode ? Functions::cbc('versions') : 'versions') => $versions, + ($bytecode ? Functions::cbc('update_source') : 'update_source') => ($this->UpdateSource?->toArray($bytecode) ?? null), ]; } @@ -220,6 +280,10 @@ $object->Name = Functions::array_bc($data, 'name'); $object->LatestVersion = Functions::array_bc($data, 'latest_version'); $versions = Functions::array_bc($data, 'versions'); + $object->UpdateSource = Functions::array_bc($data, 'update_source'); + + if($object->UpdateSource !== null) + $object->UpdateSource = UpdateSource::fromArray($object->UpdateSource); if($versions !== null) { diff --git a/src/ncc/Objects/PackageLock/VersionEntry.php b/src/ncc/Objects/PackageLock/VersionEntry.php index c4fe59f..a90a22b 100644 --- a/src/ncc/Objects/PackageLock/VersionEntry.php +++ b/src/ncc/Objects/PackageLock/VersionEntry.php @@ -1,4 +1,24 @@ $this->Version, - ($bytecode ? Functions::cbc('compiler') : 'compiler') => $this->Compiler->toArray($bytecode), + ($bytecode ? Functions::cbc('compiler') : 'compiler') => $this->Compiler->toArray(), ($bytecode ? Functions::cbc('dependencies') : 'dependencies') => $dependencies, ($bytecode ? Functions::cbc('execution_units') : 'execution_units') => $execution_units, ($bytecode ? Functions::cbc('main_execution_policy') : 'main_execution_policy') => $this->MainExecutionPolicy, diff --git a/src/ncc/Objects/PhpConfiguration.php b/src/ncc/Objects/PhpConfiguration.php index e6d4e50..329ae8e 100644 --- a/src/ncc/Objects/PhpConfiguration.php +++ b/src/ncc/Objects/PhpConfiguration.php @@ -1,4 +1,24 @@ Build->Main !== null) + { + if($this->ExecutionPolicies == null || count($this->ExecutionPolicies) == 0) + { + if($throw_exception) + throw new UndefinedExecutionPolicyException(sprintf('Build configuration build.main uses an execution policy "%s" but no policies are defined', $this->Build->Main)); + return false; + } + + + $found = false; + foreach($this->ExecutionPolicies as $policy) + { + if($policy->Name == $this->Build->Main) + { + $found = true; + break; + } + } + + if(!$found) + { + if($throw_exception) + throw new UndefinedExecutionPolicyException(sprintf('Build configuration build.main points to a undefined execution policy "%s"', $this->Build->Main)); + return false; + } + + if($this->Build->Main == BuildConfigurationValues::AllConfigurations) + { + if($throw_exception) + throw new InvalidBuildConfigurationException(sprintf('Build configuration build.main cannot be set to "%s"', BuildConfigurationValues::AllConfigurations)); + return false; + } + } + return true; } diff --git a/src/ncc/Objects/ProjectConfiguration/Assembly.php b/src/ncc/Objects/ProjectConfiguration/Assembly.php index 46e4af4..ff2259f 100644 --- a/src/ncc/Objects/ProjectConfiguration/Assembly.php +++ b/src/ncc/Objects/ProjectConfiguration/Assembly.php @@ -1,4 +1,24 @@ Configurations = []; } + /** + * Adds a new dependency to the build, if it doesn't already exist + * + * @param Dependency $dependency + * @return void + */ + public function addDependency(Dependency $dependency): void + { + foreach($this->Dependencies as $dep) + { + if($dep->Name == $dependency->Name) + { + $this->removeDependency($dep->Name); + break; + } + } + + $this->Dependencies[] = $dependency; + } + + /** + * Removes a dependency from the build + * + * @param string $name + * @return void + */ + private function removeDependency(string $name): void + { + foreach($this->Dependencies as $key => $dep) + { + if($dep->Name == $name) + { + unset($this->Dependencies[$key]); + return; + } + } + } + /** * Validates the build configuration object * * @param bool $throw_exception * @return bool + * @throws BuildConfigurationNotFoundException + * @throws InvalidBuildConfigurationException * @throws InvalidConstantNameException * @throws InvalidProjectBuildConfiguration */ public function validate(bool $throw_exception=True): bool { - // TODO: Implement validation for Configurations, Dependencies and ExcludedFiles - // Check the defined constants foreach($this->DefineConstants as $name => $value) { @@ -140,6 +200,37 @@ } } + foreach($this->Configurations as $configuration) + { + try + { + if (!$configuration->validate($throw_exception)) + return false; + } + catch (InvalidBuildConfigurationException $e) + { + throw new InvalidBuildConfigurationException(sprintf('Error in build configuration \'%s\'', $configuration->Name), $e); + } + } + + if($this->DefaultConfiguration == null) + { + if($throw_exception) + throw new InvalidProjectBuildConfiguration('The default build configuration is not set'); + + return false; + } + + if(!Validate::nameFriendly($this->DefaultConfiguration)) + { + if($throw_exception) + throw new InvalidProjectBuildConfiguration('The default build configuration name \'' . $this->DefaultConfiguration . '\' is not valid'); + + return false; + } + + $this->getBuildConfiguration($this->DefaultConfiguration); + return true; } @@ -206,7 +297,7 @@ if($this->Scope !== null) $ReturnResults[($bytecode ? Functions::cbc('scope') : 'scope')] = $this->Scope; if($this->Main !== null) - $ReturnResults[($bytecode ? Functions::cbc('main') : 'main')] = $this->Main; + $ReturnResults[($bytecode ? Functions::cbc('main') : 'main')] = $this->Main; if($this->DefineConstants !== null && count($this->DefineConstants) > 0) $ReturnResults[($bytecode ? Functions::cbc('define_constants') : 'define_constants')] = $this->DefineConstants; if($this->PreBuild !== null && count($this->PreBuild) > 0) diff --git a/src/ncc/Objects/ProjectConfiguration/BuildConfiguration.php b/src/ncc/Objects/ProjectConfiguration/Build/BuildConfiguration.php similarity index 53% rename from src/ncc/Objects/ProjectConfiguration/BuildConfiguration.php rename to src/ncc/Objects/ProjectConfiguration/Build/BuildConfiguration.php index 8991c63..22ed725 100644 --- a/src/ncc/Objects/ProjectConfiguration/BuildConfiguration.php +++ b/src/ncc/Objects/ProjectConfiguration/Build/BuildConfiguration.php @@ -1,10 +1,34 @@ Dependencies = []; } - // TODO: Add a function to validate the object data + /** + * Validates the BuildConfiguration object + * + * @param bool $throw_exception + * @return bool + * @throws InvalidBuildConfigurationException + */ + public function validate(bool $throw_exception=True): bool + { + if(!Validate::nameFriendly($this->Name)) + { + if($throw_exception) + throw new InvalidBuildConfigurationException(sprintf('Invalid build configuration name "%s"', $this->Name)); + + return False; + } + + if(!Validate::pathName($this->OutputPath)) + { + if($throw_exception) + throw new InvalidBuildConfigurationException(sprintf('\'output_path\' contains an invalid path name in %s', $this->Name)); + + return False; + } + + if($this->DefineConstants !== null && !is_array($this->DefineConstants)) + { + if($throw_exception) + throw new InvalidBuildConfigurationException(sprintf('\'define_constants\' must be an array in %s', $this->Name)); + + return False; + } + + if($this->ExcludeFiles !== null && !is_array($this->ExcludeFiles)) + { + if($throw_exception) + throw new InvalidBuildConfigurationException(sprintf('\'exclude_files\' must be an array in %s', $this->Name)); + + return False; + } + + if($this->PreBuild !== null && !is_array($this->PreBuild)) + { + if($throw_exception) + throw new InvalidBuildConfigurationException(sprintf('\'pre_build\' must be an array in %s', $this->Name)); + + return False; + } + + if($this->PostBuild !== null && !is_array($this->PostBuild)) + { + if($throw_exception) + throw new InvalidBuildConfigurationException(sprintf('\'post_build\' must be an array in %s', $this->Name)); + + return False; + } + + if($this->Dependencies !== null && !is_array($this->Dependencies)) + { + if($throw_exception) + throw new InvalidBuildConfigurationException(sprintf('\'dependencies\' must be an array in %s', $this->Name)); + + return False; + } + + /** @var Dependency $dependency */ + foreach($this->Dependencies as $dependency) + { + try + { + if (!$dependency->validate($throw_exception)) + return False; + } + catch (InvalidDependencyConfiguration $e) + { + if($throw_exception) + throw new InvalidBuildConfigurationException(sprintf('Invalid dependency configuration in %s: %s', $this->Name, $e->getMessage())); + + return False; + } + } + + return True; + } /** * Returns an array representation of the object diff --git a/src/ncc/Objects/ProjectConfiguration/Compiler.php b/src/ncc/Objects/ProjectConfiguration/Compiler.php index 9796150..64ce759 100644 --- a/src/ncc/Objects/ProjectConfiguration/Compiler.php +++ b/src/ncc/Objects/ProjectConfiguration/Compiler.php @@ -1,4 +1,24 @@ Extension !== null && strlen($this->Extension) > 0) diff --git a/src/ncc/Objects/ProjectConfiguration/Dependency.php b/src/ncc/Objects/ProjectConfiguration/Dependency.php index 3fa3116..46b6f8c 100644 --- a/src/ncc/Objects/ProjectConfiguration/Dependency.php +++ b/src/ncc/Objects/ProjectConfiguration/Dependency.php @@ -1,11 +1,32 @@ Name)) + { + if($throw_exception) + throw new InvalidDependencyConfiguration(sprintf('Invalid dependency name "%s"', $this->Name)); + + return false; + } + + if($this->Version !== null && !Validate::version($this->Version)) + { + if($throw_exception) + throw new InvalidDependencyConfiguration(sprintf('Invalid dependency version "%s"', $this->Version)); + + return false; + } + + return true; + } } \ No newline at end of file diff --git a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php index e0b0a58..efc175c 100644 --- a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php +++ b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php @@ -1,10 +1,29 @@ Name = Functions::array_bc($data, 'name'); $object->Runner = Functions::array_bc($data, 'runner'); $object->Message = Functions::array_bc($data, 'message'); - $object->Execute = Functions::array_bc($data, 'exec'); + $object->Execute = Functions::array_bc($data, 'execute'); $object->ExitHandlers = Functions::array_bc($data, 'exit_handlers'); if($object->Execute !== null) diff --git a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php index 8fe5b02..6839d88 100644 --- a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php +++ b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php @@ -1,4 +1,24 @@ Tty = false; $this->Silent = false; $this->Timeout = null; + $this->IdleTimeout = null; $this->WorkingDirectory = "%CWD%"; } @@ -82,6 +115,9 @@ if($this->Options !== null) $results[($bytecode ? Functions::cbc("options") : "options")] = $this->Options; + if($this->EnvironmentVariables !== null) + $results[($bytecode ? Functions::cbc("environment_variables") : "environment_variables")] = $this->EnvironmentVariables; + if($this->Silent !== null) $results[($bytecode ? Functions::cbc("silent") : "silent")] = (bool)$this->Silent; @@ -91,6 +127,9 @@ if($this->Timeout !== null) $results[($bytecode ? Functions::cbc("timeout") : "timeout")] = (int)$this->Timeout; + if($this->IdleTimeout !== null) + $results[($bytecode ? Functions::cbc("idle_timeout") : "idle_timeout")] = (int)$this->IdleTimeout; + return $results; } @@ -107,9 +146,11 @@ $object->Target = Functions::array_bc($data, 'target'); $object->WorkingDirectory = Functions::array_bc($data, 'working_directory'); $object->Options = Functions::array_bc($data, 'options'); + $object->EnvironmentVariables = Functions::array_bc($data, 'environment_variables'); $object->Silent = Functions::array_bc($data, 'silent'); $object->Tty = Functions::array_bc($data, 'tty'); $object->Timeout = Functions::array_bc($data, 'timeout'); + $object->IdleTimeout = Functions::array_bc($data, 'idle_timeout'); return $object; } diff --git a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/ExitHandle.php b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/ExitHandle.php index 98ea264..176858f 100644 --- a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/ExitHandle.php +++ b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/ExitHandle.php @@ -1,4 +1,24 @@ Compiler->toArray($bytecode); + $ReturnResults[($bytecode ? Functions::cbc('compiler') : 'compiler')] = $this->Compiler->toArray(); $ReturnResults[($bytecode ? Functions::cbc('options') : 'options')] = $this->Options; + if($this->UpdateSource !== null) + $ReturnResults[($bytecode ? Functions::cbc('update_source') : 'update_source')] = $this->UpdateSource->toArray($bytecode); + return $ReturnResults; } @@ -89,6 +117,11 @@ $ProjectObject->Options = Functions::array_bc($data, 'options'); } + if(Functions::array_bc($data, 'update_source') !== null) + { + $ProjectObject->UpdateSource = UpdateSource::fromArray(Functions::array_bc($data, 'update_source')); + } + return $ProjectObject; } } \ No newline at end of file diff --git a/src/ncc/Objects/ProjectConfiguration/UpdateSource.php b/src/ncc/Objects/ProjectConfiguration/UpdateSource.php index b9344e7..1cb0c97 100644 --- a/src/ncc/Objects/ProjectConfiguration/UpdateSource.php +++ b/src/ncc/Objects/ProjectConfiguration/UpdateSource.php @@ -1,8 +1,78 @@ $this->Source, + ($bytecode ? Functions::cbc('repository') : 'repository') => ($this->Repository?->toArray($bytecode)) + ]; + } + + + /** + * Constructs object from an array representation + * + * @param array $data + * @return UpdateSource + */ + public static function fromArray(array $data): UpdateSource + { + $obj = new UpdateSource(); + $obj->Source = Functions::array_bc($data, 'source'); + $obj->Repository = Functions::array_bc($data, 'repository'); + + if($obj->Repository !== null) + $obj->Repository = Repository::fromArray($obj->Repository); + + return $obj; + } } \ No newline at end of file diff --git a/src/ncc/Objects/ProjectConfiguration/UpdateSource/Repository.php b/src/ncc/Objects/ProjectConfiguration/UpdateSource/Repository.php new file mode 100644 index 0000000..395e215 --- /dev/null +++ b/src/ncc/Objects/ProjectConfiguration/UpdateSource/Repository.php @@ -0,0 +1,90 @@ + $this->Name, + ($bytecode ? Functions::cbc('type') : 'type') => $this->Type, + ($bytecode ? Functions::cbc('host') : 'host') => $this->Host, + ($bytecode ? Functions::cbc('ssl') : 'ssl') => $this->SSL + ]; + } + + /** + * Constructs object from an array representation + * + * @param array $data + * @return Repository + */ + public static function fromArray(array $data): self + { + $obj = new self(); + $obj->Name = Functions::array_bc($data, 'name'); + $obj->Type = Functions::array_bc($data, 'type'); + $obj->Host = Functions::array_bc($data, 'host'); + $obj->SSL = Functions::array_bc($data, 'ssl'); + return $obj; + } + } \ No newline at end of file diff --git a/src/ncc/Objects/ProjectDetectionResults.php b/src/ncc/Objects/ProjectDetectionResults.php new file mode 100644 index 0000000..b5bb92e --- /dev/null +++ b/src/ncc/Objects/ProjectDetectionResults.php @@ -0,0 +1,78 @@ +Files = new Files(); + } + } \ No newline at end of file diff --git a/src/ncc/Objects/RepositoryQueryResults/Files.php b/src/ncc/Objects/RepositoryQueryResults/Files.php new file mode 100644 index 0000000..bc11788 --- /dev/null +++ b/src/ncc/Objects/RepositoryQueryResults/Files.php @@ -0,0 +1,70 @@ +ExecutionPolicyName = 'main'; + $this->Registered = false; + } + + /** + * Returns a string representation of the object + * + * @param bool $bytecode + * @return array + */ + public function toArray(bool $bytecode=false): array + { + return [ + ($bytecode ? Functions::cbc('package') : 'package') => $this->Package, + ($bytecode ? Functions::cbc('registered') : 'registered') => $this->Registered, + ($bytecode ? Functions::cbc('execution_policy_name') : 'execution_policy_name') => $this->ExecutionPolicyName + ]; + } + + /** + * Constructs a new SymlinkEntry from an array representation + * + * @param array $data + * @return SymlinkEntry + */ + public static function fromArray(array $data): SymlinkEntry + { + $entry = new SymlinkEntry(); + + $entry->Package = Functions::array_bc($data, 'package'); + $entry->Registered = (bool)Functions::array_bc($data, 'registered'); + $entry->ExecutionPolicyName = Functions::array_bc($data, 'execution_policy_name'); + + return $entry; + } + + } \ No newline at end of file diff --git a/src/ncc/Objects/Vault.php b/src/ncc/Objects/Vault.php index 8a60105..f3e9517 100644 --- a/src/ncc/Objects/Vault.php +++ b/src/ncc/Objects/Vault.php @@ -1,9 +1,35 @@ Version = Versions::CredentialsStoreVersion; $this->Entries = []; } + /** + * Adds a new entry to the vault + * + * @param string $name + * @param PasswordInterface $password + * @param bool $encrypt + * @return bool + * @noinspection PhpUnused + */ + public function addEntry(string $name, PasswordInterface $password, bool $encrypt=true): bool + { + // Check if the entry already exists + foreach($this->Entries as $entry) + { + if($entry->getName() === $name) + return false; + } + + // Create the new entry + $entry = new Entry(); + $entry->setName($name); + $entry->setEncrypted($encrypt); + $entry->setAuthentication($password); + + // Add the entry to the vault + $this->Entries[] = $entry; + return true; + } + + /** + * Deletes an entry from the vault + * + * @param string $name + * @return bool + * @noinspection PhpUnused + */ + public function deleteEntry(string $name): bool + { + // Find the entry + foreach($this->Entries as $index => $entry) + { + if($entry->getName() === $name) + { + // Remove the entry + unset($this->Entries[$index]); + return true; + } + } + + // Entry not found + return false; + } + + /** + * Returns all the entries in the vault + * + * @return array|Entry[] + * @noinspection PhpUnused + */ + public function getEntries(): array + { + return $this->Entries; + } + + /** + * Returns an existing entry from the vault + * + * @param string $name + * @return Entry|null + */ + public function getEntry(string $name): ?Entry + { + foreach($this->Entries as $entry) + { + if($entry->getName() === $name) + return $entry; + } + + return null; + } + + /** + * Authenticates an entry in the vault + * + * @param string $name + * @param string $password + * @return bool + * @throws RuntimeException + * @noinspection PhpUnused + */ + public function authenticate(string $name, string $password): bool + { + $entry = $this->getEntry($name); + if($entry === null) + return false; + + if($entry->getPassword() === null) + { + if($entry->isEncrypted() && !$entry->isCurrentlyDecrypted()) + { + return $entry->unlock($password); + } + } + + $input = []; + switch($entry->getPassword()->getAuthenticationType()) + { + case AuthenticationType::UsernamePassword: + $input = ['password' => $password]; + break; + case AuthenticationType::AccessToken: + $input = ['token' => $password]; + break; + } + + return $entry->authenticate($input); + } + /** * Returns an array representation of the object * + * @param bool $bytecode * @return array */ - public function toArray(): array + public function toArray(bool $bytecode=false): array { - $Entries = []; - + $entries = []; foreach($this->Entries as $entry) { - $Entries[] = $entry->toArray(); + $entries[] = $entry->toArray($bytecode); } return [ - 'version' => $this->Version, - 'entries' => $Entries + ($bytecode ? Functions::cbc('version') : 'version') => $this->Version, + ($bytecode ? Functions::cbc('entries') : 'entries') => $entries, ]; } /** - * Constructs an object from an array representation + * Constructs a new object from an array * - * @param array $data + * @param array $array * @return Vault */ - public static function fromArray(array $data): Vault + public static function fromArray(array $array): Vault { - $VaultObject = new Vault(); + $vault = new Vault(); + $vault->Version = Functions::array_bc($array, 'version'); + $entries = Functions::array_bc($array, 'entries'); + $vault->Entries = []; - if(isset($data['version'])) - $VaultObject->Version = $data['version']; + foreach($entries as $entry) + $vault->Entries[] = Entry::fromArray($entry); - if(isset($data['entries'])) - { - foreach($data['entries'] as $entry) - { - $VaultObject->Entries[] = Entry::fromArray($entry); - } - } - - return $VaultObject; + return $vault; } + } \ No newline at end of file diff --git a/src/ncc/Objects/Vault/DefaultEntry.php b/src/ncc/Objects/Vault/DefaultEntry.php deleted file mode 100644 index c834f0d..0000000 --- a/src/ncc/Objects/Vault/DefaultEntry.php +++ /dev/null @@ -1,60 +0,0 @@ - $this->Alias, - 'source' => $this->Source - ]; - } - - /** - * Constructs the object from an array representation - * - * @param array $data - * @return DefaultEntry - */ - public static function fromArray(array $data): DefaultEntry - { - $DefaultEntryObject = new DefaultEntry(); - - if(isset($data['alias'])) - { - $DefaultEntryObject->Alias = $data['alias']; - } - - if(isset($data['source'])) - { - $DefaultEntryObject->Source = $data['source']; - } - - return $DefaultEntryObject; - } - - } \ No newline at end of file diff --git a/src/ncc/Objects/Vault/Entry.php b/src/ncc/Objects/Vault/Entry.php index 27c778f..4445ca7 100644 --- a/src/ncc/Objects/Vault/Entry.php +++ b/src/ncc/Objects/Vault/Entry.php @@ -1,127 +1,355 @@ Encrypted = true; + $this->IsCurrentlyDecrypted = true; + } + + /** + * Test Authenticates the entry + * + * For UsernamePassword the $input parameter expects an array with the keys 'username' and 'password' + * For AccessToken the $input parameter expects an array with the key 'token' + * + * @param array $input + * @return bool + * @noinspection PhpUnused + */ + public function authenticate(array $input): bool + { + if(!$this->IsCurrentlyDecrypted) + return false; + + if($this->Password == null) + return false; + + switch($this->Password->getAuthenticationType()) + { + case AuthenticationType::UsernamePassword: + if(!($this->Password instanceof UsernamePassword)) + return false; + + $username = $input['username'] ?? null; + $password = $input['password'] ?? null; + + if($username === null && $password === null) + return false; + + if($username == null) + return $password == $this->Password->getPassword(); + + if($password == null) + return $username == $this->Password->getUsername(); + + return $username == $this->Password->getUsername() && $password == $this->Password->getPassword(); + + case AuthenticationType::AccessToken: + if(!($this->Password instanceof AccessToken)) + return false; + + $token = $input['token'] ?? null; + + if($token === null) + return false; + + return $token == $this->Password->AccessToken; + + default: + return false; + + } + } + + /** + * @param PasswordInterface $password + * @return void + */ + public function setAuthentication(PasswordInterface $password): void + { + $this->Password = $password; + } + + /** + * @return bool + * @noinspection PhpUnused + */ + public function isCurrentlyDecrypted(): bool + { + return $this->IsCurrentlyDecrypted; + } + + /** + * Locks the entry by encrypting the password + * + * @return bool + */ + public function lock(): bool + { + if($this->Password == null) + return false; + + if($this->Encrypted) + return false; + + if(!$this->IsCurrentlyDecrypted) + return false; + + if(!($this->Password instanceof PasswordInterface)) + return false; + + $this->Password = $this->encrypt(); + return true; + } + + /** + * Unlocks the entry by decrypting the password + * + * @param string $password + * @return bool + * @throws RuntimeException + * @noinspection PhpUnused + */ + public function unlock(string $password): bool + { + if($this->Password == null) + return false; + + if(!$this->Encrypted) + return false; + + if($this->IsCurrentlyDecrypted) + return false; + + if(!is_string($this->Password)) + return false; + + try + { + $password = Crypto::decryptWithPassword($this->Password, $password, true); + } + catch (EnvironmentIsBrokenException $e) + { + throw new RuntimeException('Cannot decrypt password', $e); + } + catch (WrongKeyOrModifiedCiphertextException $e) + { + unset($e); + return false; + } + + $this->Password = ZiProto::decode($password); + $this->IsCurrentlyDecrypted = true; + + return true; + } + + /** + * Returns the password object as an encrypted binary string + * + * @return string|null + */ + private function encrypt(): ?string + { + if(!$this->IsCurrentlyDecrypted) + return false; + + if($this->Password == null) + return false; + + if(!($this->Password instanceof PasswordInterface)) + return null; + + $data = ZiProto::encode($this->Password->toArray(true)); + return Crypto::encryptWithPassword($data, (string)$this->Password, true); + } + + /** + * Returns an array representation of the object + * + * @param bool $bytecode + * @return array + */ + public function toArray(bool $bytecode=false): array + { + if($this->Password !== null) + { + if($this->Encrypted && $this->IsCurrentlyDecrypted) + { + $password = $this->encrypt(); + } + elseif($this->Encrypted) + { + $password = $this->Password; + } + else + { + $password = $this->Password->toArray(true); + } + } + else + { + $password = $this->Password; + } + return [ - 'alias' => $this->Alias, - 'source' => $this->Source, - 'source_host' => $this->SourceHost, - 'authentication_type' => $this->AuthenticationType, - 'encrypted' => $this->Encrypted, - 'authentication' => $this->Authentication + ($bytecode ? Functions::cbc('name') : 'name') => $this->Name, + ($bytecode ? Functions::cbc('encrypted') : 'encrypted') => $this->Encrypted, + ($bytecode ? Functions::cbc('password') : 'password') => $password, ]; } /** - * Returns an array representation of the object + * Constructs an object from an array representation * * @param array $data * @return Entry */ - public static function fromArray(array $data): Entry + public static function fromArray(array $data): self { - $EntryObject = new Entry(); + $self = new self(); - if(isset($data['alias'])) + $self->Name = Functions::array_bc($data, 'name'); + $self->Encrypted = Functions::array_bc($data, 'encrypted'); + $password = Functions::array_bc($data, 'password'); + + if($password !== null) { - $EntryObject->Alias = $data['alias']; + if($self->Encrypted) + { + $self->Password = $password; + $self->IsCurrentlyDecrypted = false; + } + elseif(gettype($password) == 'array') + { + $self->Password = match (Functions::array_bc($password, 'authentication_type')) + { + AuthenticationType::UsernamePassword => UsernamePassword::fromArray($password), + AuthenticationType::AccessToken => AccessToken::fromArray($password) + }; + } } - if(isset($data['source'])) - { - $EntryObject->Source = $data['source']; - } + return $self; + } - if(isset($data['source_host'])) - { - $EntryObject->SourceHost = $data['source_host']; - } + /** + * @return bool + */ + public function isEncrypted(): bool + { + return $this->Encrypted; + } - if(isset($data['authentication_type'])) - { - $EntryObject->AuthenticationType = $data['authentication_type']; - } + /** + * Returns false if the entry needs to be decrypted first + * + * @param bool $Encrypted + * @return bool + */ + public function setEncrypted(bool $Encrypted): bool + { + if(!$this->IsCurrentlyDecrypted) + return false; - if(isset($data['encrypted'])) - { - $EntryObject->Encrypted = $data['encrypted']; - } + $this->Encrypted = $Encrypted; + return true; + } - if(isset($data['authentication'])) - { - $EntryObject->Authentication = $data['authentication']; - } + /** + * @return string + */ + public function getName(): string + { + return $this->Name; + } - return $EntryObject; + /** + * @param string $Name + */ + public function setName(string $Name): void + { + $this->Name = $Name; + } + + /** + * @return PasswordInterface|null + */ + public function getPassword(): ?PasswordInterface + { + if(!$this->IsCurrentlyDecrypted) + return null; + + return $this->Password; } } \ No newline at end of file diff --git a/src/ncc/Objects/Vault/Password/AccessToken.php b/src/ncc/Objects/Vault/Password/AccessToken.php new file mode 100644 index 0000000..66b2d69 --- /dev/null +++ b/src/ncc/Objects/Vault/Password/AccessToken.php @@ -0,0 +1,102 @@ + AuthenticationType::AccessToken, + ($bytecode ? Functions::cbc('access_token') : 'access_token') => $this->AccessToken, + ]; + } + + /** + * Constructs an object from an array representation + * + * @param array $data + * @return static + */ + public static function fromArray(array $data): self + { + $object = new self(); + + $object->AccessToken = Functions::array_bc($data, 'access_token'); + + return $object; + } + + /** + * @return string + */ + public function getAccessToken(): string + { + return $this->AccessToken; + } + + /** + * @inheritDoc + */ + public function getAuthenticationType(): string + { + return AuthenticationType::AccessToken; + } + + /** + * Returns a string representation of the object + * + * @return string + */ + public function __toString(): string + { + return $this->AccessToken; + } + + /** + * @param string $AccessToken + */ + public function setAccessToken(string $AccessToken): void + { + $this->AccessToken = $AccessToken; + } + } \ No newline at end of file diff --git a/src/ncc/Objects/Vault/Password/UsernamePassword.php b/src/ncc/Objects/Vault/Password/UsernamePassword.php new file mode 100644 index 0000000..de11f39 --- /dev/null +++ b/src/ncc/Objects/Vault/Password/UsernamePassword.php @@ -0,0 +1,129 @@ + AuthenticationType::UsernamePassword, + ($bytecode ? Functions::cbc('username') : 'username') => $this->Username, + ($bytecode ? Functions::cbc('password') : 'password') => $this->Password, + ]; + } + + /** + * Constructs an object from an array representation + * + * @param array $data + * @return static + */ + public static function fromArray(array $data): self + { + $instance = new self(); + + $instance->Username = Functions::array_bc($data, 'username'); + $instance->Password = Functions::array_bc($data, 'password'); + + return $instance; + } + + /** + * @return string + * @noinspection PhpUnused + */ + public function getUsername(): string + { + return $this->Username; + } + + /** + * @return string + * @noinspection PhpUnused + */ + public function getPassword(): string + { + return $this->Password; + } + + /** + * @inheritDoc + */ + public function getAuthenticationType(): string + { + return AuthenticationType::UsernamePassword; + } + + /** + * Returns a string representation of the object + * + * @return string + */ + public function __toString(): string + { + return $this->Password; + } + + /** + * @param string $Username + */ + public function setUsername(string $Username): void + { + $this->Username = $Username; + } + + /** + * @param string $Password + */ + public function setPassword(string $Password): void + { + $this->Password = $Password; + } + } \ No newline at end of file diff --git a/src/ncc/Runtime.php b/src/ncc/Runtime.php new file mode 100644 index 0000000..b9ab941 --- /dev/null +++ b/src/ncc/Runtime.php @@ -0,0 +1,228 @@ +getPackage($package)->getLatestVersion(); + + $entry = "$package=$version"; + + return isset(self::$imported_packages[$entry]); + } + + /** + * Adds a package to the imported packages list + * + * @param string $package + * @param string $version + * @return void + */ + private static function addImport(string $package, string $version): void + { + $entry = "$package=$version"; + self::$imported_packages[$entry] = true; + } + + + /** + * @param string $package + * @param string $version + * @param array $options + * @return void + * @throws ImportException + */ + public static function import(string $package, string $version=Versions::Latest, array $options=[]): void + { + try + { + $package_entry = self::getPackageManager()->getPackage($package); + } + catch (PackageLockException $e) + { + throw new ImportException(sprintf('Failed to import package "%s" due to a package lock exception: %s', $package, $e->getMessage()), $e); + } + if($package_entry == null) + { + throw new ImportException(sprintf("Package '%s' not found", $package)); + } + + if($version == Versions::Latest) + $version = $package_entry->getLatestVersion(); + + try + { + /** @var VersionEntry $version_entry */ + $version_entry = $package_entry->getVersion($version); + + if($version_entry == null) + throw new VersionNotFoundException(); + } + catch (VersionNotFoundException $e) + { + throw new ImportException(sprintf('Version %s of %s is not installed', $version, $package), $e); + } + + try + { + if (self::isImported($package, $version)) + return; + } + catch (PackageLockException $e) + { + throw new ImportException(sprintf('Failed to check if package %s is imported', $package), $e); + } + + if($version_entry->Dependencies !== null && count($version_entry->Dependencies) > 0) + { + // Import all dependencies first + /** @var Dependency $dependency */ + foreach($version_entry->Dependencies as $dependency) + self::import($dependency->PackageName, $dependency->Version, $options); + } + + try + { + switch($version_entry->Compiler->Extension) + { + case CompilerExtensions::PHP: + PhpRuntime::import($version_entry, $options); + break; + + default: + throw new ImportException(sprintf('Compiler extension %s is not supported in this runtime', $version_entry->Compiler->Extension)); + } + } + catch(Exception $e) + { + throw new ImportException(sprintf('Failed to import package %s', $package), $e); + } + + self::addImport($package, $version); + } + + /** + * Returns the data path of the package + * + * @param string $package + * @return string + * @throws Exceptions\InvalidPackageNameException + * @throws Exceptions\InvalidScopeException + * @throws PackageLockException + * @throws PackageNotFoundException + */ + public static function getDataPath(string $package): string + { + $package = self::getPackageManager()->getPackage($package); + + if($package == null) + throw new PackageNotFoundException('Package not found (null entry error, possible bug)'); + + return $package->getDataPath(); + } + + /** + * @return PackageManager + */ + private static function getPackageManager(): PackageManager + { + if(self::$package_manager == null) + self::$package_manager = new PackageManager(); + return self::$package_manager; + } + + /** + * Returns an array of all the packages that is currently imported + * + * @return array + */ + public static function getImportedPackages(): array + { + return array_keys(self::$imported_packages); + } + + /** + * Returns a registered constant + * + * @param string $package + * @param string $name + * @return string|null + */ + public static function getConstant(string $package, string $name): ?string + { + return Constants::get($package, $name); + } + + /** + * Registers a new constant + * + * @param string $package + * @param string $name + * @param string $value + * @return void + * @throws ConstantReadonlyException + * @throws InvalidConstantNameException + */ + public static function setConstant(string $package, string $name, string $value): void + { + Constants::register($package, $name, $value); + } + } \ No newline at end of file diff --git a/src/ncc/Runtime/Constants.php b/src/ncc/Runtime/Constants.php index 312b443..2b8195a 100644 --- a/src/ncc/Runtime/Constants.php +++ b/src/ncc/Runtime/Constants.php @@ -1,4 +1,24 @@ getValue(); + + return null; + } } \ No newline at end of file diff --git a/src/ncc/Utilities/Base64.php b/src/ncc/Utilities/Base64.php index 9c75af3..d827ea5 100644 --- a/src/ncc/Utilities/Base64.php +++ b/src/ncc/Utilities/Base64.php @@ -1,6 +1,26 @@ self::$largestTickLength) + { self::$largestTickLength = strlen($tick_time); + } + if(strlen($tick_time) < self::$largestTickLength) + { /** @noinspection PhpRedundantOptionalArgumentInspection */ $tick_time = str_pad($tick_time, (strlen($tick_time) + (self::$largestTickLength - strlen($tick_time))), ' ', STR_PAD_RIGHT); + } - return '[' . $tick_time . ' - ' . date('TH:i:sP') . '] ' . $input; + $fmt_tick = $tick_time; + if(self::$lastTickTime !== null) + { + $timeDiff = microtime(true) - self::$lastTickTime; + + if ($timeDiff > 1.0) + { + $fmt_tick = Console::formatColor($tick_time, ConsoleColors::LightRed); + } + elseif ($timeDiff > 0.5) + { + $fmt_tick = Console::formatColor($tick_time, ConsoleColors::LightYellow); + } + } + + self::$lastTickTime = $tick_time; + return '[' . $fmt_tick . '] ' . $input; } /** @@ -179,6 +229,14 @@ $trace_msg .= ' > '; } + /** Apply syntax highlighting using regular expressions */ + + // Hyperlinks + $message = preg_replace('/(https?:\/\/[^\s]+)/', Console::formatColor('$1', ConsoleColors::LightBlue), $message); + + // File Paths + $message = preg_replace('/(\/[^\s]+)/', Console::formatColor('$1', ConsoleColors::LightCyan), $message); + /** @noinspection PhpUnnecessaryStringCastInspection */ $message = self::setPrefix(LogLevel::Debug, (string)$trace_msg . $message); @@ -313,16 +371,20 @@ * Prints out a detailed exception display (unfinished) * * @param Exception $e + * @param bool $sub * @return void */ - private static function outExceptionDetails(Exception $e): void + private static function outExceptionDetails(Throwable $e, bool $sub=false): void { if(!ncc::cliMode()) return; + // Exception name without namespace + $trace_header = self::formatColor($e->getFile() . ':' . $e->getLine(), ConsoleColors::Magenta); - $trace_error = self::formatColor('error: ', ConsoleColors::Red); + $trace_error = self::formatColor( 'Error: ', ConsoleColors::Red); self::out($trace_header . ' ' . $trace_error . $e->getMessage()); + self::out(sprintf('Exception: %s', get_class($e))); self::out(sprintf('Error code: %s', $e->getCode())); $trace = $e->getTrace(); if(count($trace) > 1) @@ -334,7 +396,16 @@ } } - if(Main::getArgs() !== null) + if($e->getPrevious() !== null) + { + // Check if previous is the same as the current + if($e->getPrevious()->getMessage() !== $e->getMessage()) + { + self::outExceptionDetails($e->getPrevious(), true); + } + } + + if(Main::getArgs() !== null && !$sub) { if(isset(Main::getArgs()['dbg-ex'])) { @@ -425,4 +496,53 @@ } } } + + /** + * Prompts for a password input while hiding the user's password + * + * @param string $prompt + * @return string|null + */ + public static function passwordInput(string $prompt): ?string + { + if(!ncc::cliMode()) + return null; + + // passwordInput() is not properly implemented yet, defaulting to prompt + return self::getInput($prompt); + + /** + $executable_finder = new ExecutableFinder(); + $bash_path = $executable_finder->find('bash'); + + if($bash_path == null) + { + self::outWarning('Unable to find bash executable, cannot hide password input'); + return self::getInput($prompt); + } + + $prompt = escapeshellarg($prompt); + $random = Functions::randomString(10); + $command = "$bash_path -c 'read -s -p $prompt $random && echo \$" . $random . "'"; + $password = rtrim(shell_exec($command)); + self::out((string)null); + return $password; + **/ + } + + /** + * @param array $sections + * @return void + */ + public static function outHelpSections(array $sections): void + { + if(!ncc::cliMode()) + return; + + $padding = Functions::detectParametersPadding($sections); + + foreach($sections as $section) + Console::out(' ' . $section->toString($padding)); + } + } \ No newline at end of file diff --git a/src/ncc/Utilities/Functions.php b/src/ncc/Utilities/Functions.php index fa4096c..863520b 100644 --- a/src/ncc/Utilities/Functions.php +++ b/src/ncc/Utilities/Functions.php @@ -1,26 +1,75 @@ Runner)) { + Runners::bash => BashRunner::processUnit($path, $policy), Runners::php => PhpRunner::processUnit($path, $policy), - default => throw new UnsupportedRunnerException('The runner \'' . $policy->Runner . '\' is not supported'), + Runners::perl => PerlRunner::processUnit($path, $policy), + Runners::python => PythonRunner::processUnit($path, $policy), + Runners::python2 => Python2Runner::processUnit($path, $policy), + Runners::python3 => Python3Runner::processUnit($path, $policy), + Runners::lua => LuaRunner::processUnit($path, $policy), + default => throw new RunnerExecutionException('The runner \'' . $policy->Runner . '\' is not supported'), }; } @@ -268,7 +323,7 @@ * @param Exception $e * @return array */ - public static function exceptionToArray(Exception $e): array + public static function exceptionToArray(Throwable $e): array { $exception = [ 'message' => $e->getMessage(), @@ -281,7 +336,7 @@ if($e->getPrevious() !== null) { - $exception['trace'] = self::exceptionToArray($e); + $exception['previous'] = self::exceptionToArray($e->getPrevious()); } return $exception; @@ -376,6 +431,7 @@ * @throws AccessDeniedException * @throws FileNotFoundException * @throws IOException + * @noinspection PhpUnused */ public static function loadComposerJson(string $path): ComposerJson { @@ -430,6 +486,7 @@ * * @param string $property * @return mixed|null + * @noinspection PhpMissingReturnTypeInspection */ public static function getConfigurationProperty(string $property) { @@ -452,4 +509,484 @@ return Parser::parse($version)->toString(); } + + /** + * Returns a random string + * + * @param int $length + * @return string + */ + public static function randomString(int $length = 32): string + { + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $charactersLength = strlen($characters); + $randomString = ''; + for ($i = 0; $i < $length; $i++) + { + $randomString .= $characters[rand(0, $charactersLength - 1)]; + } + return $randomString; + } + + /** + * Returns a path to a temporary directory + * + * @param bool $create + * @param bool $set_as_tmp + * @return string + * @throws InvalidScopeException + */ + public static function getTmpDir(bool $create=true, bool $set_as_tmp=true): string + { + $path = PathFinder::getCachePath() . DIRECTORY_SEPARATOR . self::randomString(16); + if($create) + { + $filesystem = new Filesystem(); + /** @noinspection PhpRedundantOptionalArgumentInspection */ + $filesystem->mkdir($path, 0777); + } + if($set_as_tmp) + RuntimeCache::setFileAsTemporary($path); + return $path; + } + + /** + * Applies the authentication to the given HTTP request. + * + * @param HttpRequest $httpRequest + * @param Entry|null $entry + * @param bool $expect_json + * @return HttpRequest + * @throws AuthenticationException + * @throws GitlabServiceException + */ + public static function prepareGitServiceRequest(HttpRequest $httpRequest, ?Entry $entry=null, bool $expect_json=true): HttpRequest + { + if($entry !== null) + { + if (!$entry->isCurrentlyDecrypted()) + throw new GitlabServiceException('The given Vault entry is not decrypted.'); + + switch ($entry->getPassword()->getAuthenticationType()) { + case AuthenticationType::AccessToken: + $httpRequest->Headers[] = "Authorization: Bearer " . $entry->getPassword(); + break; + + case AuthenticationType::UsernamePassword: + throw new AuthenticationException('Username/Password authentication is not supported'); + } + } + + if($expect_json) + { + $httpRequest->Headers[] = "Accept: application/json"; + $httpRequest->Headers[] = "Content-Type: application/json"; + } + + return $httpRequest; + } + + /** + * Downloads a file from the given URL and saves it to the given path + * + * @param string $url + * @param Entry|null $entry + * @return string + * @throws AuthenticationException + * @throws GitlabServiceException + * @throws InvalidScopeException + * @throws HttpException + */ + public static function downloadGitServiceFile(string $url, ?Entry $entry=null): string + { + $out_path = Functions::getTmpDir() . "/" . basename($url); + + $httpRequest = new HttpRequest(); + $httpRequest->Url = $url; + $httpRequest->Type = HttpRequestType::GET; + $httpRequest = Functions::prepareGitServiceRequest($httpRequest, $entry, false); + + Console::out('Downloading file ' . $url); + HttpClient::download($httpRequest, $out_path); + + return $out_path; + } + + /** + * @param string $path + * @return string|null + * @throws ArchiveException + * @throws UnsupportedArchiveException + */ + public static function extractArchive(string $path): ?string + { + $executable_finder = new ExecutableFinder(); + $unzip_executable = $executable_finder->find('unzip'); + $tar_executable = $executable_finder->find('tar'); + $out_path = dirname($path); + $filesystem = new Filesystem(); + + if(!$filesystem->exists($out_path)) + $filesystem->mkdir($out_path); + + RuntimeCache::setFileAsTemporary($out_path); + + $mimeType = mime_content_type($path); + $supportedTypes = []; + + if($unzip_executable !== null) + { + $supportedTypes = array_merge($supportedTypes, [ + 'application/zip', + 'application/x-zip', + 'application/x-zip-compressed', + 'application/octet-stream', + 'application/x-compress', + 'application/x-compressed', + 'multipart/x-zip' + ]); + } + else + { + if(RuntimeCache::get('warning_zip_shown') !== true) + { + Console::out('unzip executable not found. ZIP archives will not be supported.'); + RuntimeCache::set('warning_zip_shown', true); + } + } + + if($tar_executable !== null) + { + $supportedTypes = array_merge($supportedTypes, [ + 'application/x-tar', + 'application/x-gzip', + 'application/x-bzip2', + 'application/x-xz' + ]); + } + else + { + if(RuntimeCache::get('warning_tar_shown') !== true) + { + Console::outWarning('tar executable not found. TAR archives will not be supported.'); + RuntimeCache::set('warning_tar_shown', true); + } + } + + if (!in_array($mimeType, $supportedTypes)) + throw new UnsupportedArchiveException("Unsupported archive type: $mimeType"); + + $command = match ($mimeType) { + 'application/zip' => [$unzip_executable, $path, '-d', $out_path], + 'application/x-tar' => [$tar_executable, '--verbose', '-xf', $path, '-C', $out_path], + 'application/x-gzip' => [$tar_executable, '--verbose', '-xzf', $path, '-C', $out_path], + 'application/x-bzip2' => [$tar_executable, '--verbose', '-xjf', $path, '-C', $out_path], + default => throw new UnsupportedArchiveException("Unsupported archive type: $mimeType"), + }; + + Console::out("Extracting archive $path"); + $process = new Process($command); + + // display the output of the command + $process->run(function ($type, $buffer) { + Console::outVerbose($buffer); + }); + + if (!$process->isSuccessful()) + throw new ArchiveException($process->getErrorOutput()); + + return $out_path; + } + + /** + * Scans the given directory for files and returns the found file + * + * @param string $path + * @param array $files + * @return string|null + */ + public static function searchDirectory(string $path, array $files): ?string + { + if (!is_dir($path)) + return null; + + // Search files in the given directory recursively + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); + foreach ($iterator as $file) + { + if (in_array($file->getFilename(), $files)) + return $file->getPathname(); + } + + return null; + } + + /** + * Attempts to convert a weird version number to a standard version number + * + * @param $version + * @return string + */ + public static function convertToSemVer($version): string + { + if(stripos($version, 'v') === 0) + $version = substr($version, 1); + if(!Validate::version($version)) + { + $parts = explode('.', $version); + $major = (string)null; + $minor = (string)null; + $patch = (string)null; + + if(count($parts) >= 1) + $major = $parts[0]; + if(count($parts) >= 2) + $minor = $parts[1]; + if(count($parts) >= 3) + $patch = $parts[2]; + + // Assemble the SemVer compatible string + $version = "$major.$minor.$patch"; + } + if(!Validate::version($version)) + return '1.0.0'; + + return $version; + } + + /** + * Returns a complete RepositoryQueryResults object + * + * @param RemotePackageInput $packageInput + * @param DefinedRemoteSource $definedRemoteSource + * @param Entry|null $entry + * @return RepositoryQueryResults + */ + public static function getRepositoryQueryResults(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry): RepositoryQueryResults + { + $results = new RepositoryQueryResults(); + + switch($definedRemoteSource->Type) + { + case DefinedRemoteSourceType::Github: + $source = GithubService::class; + break; + + case DefinedRemoteSourceType::Gitlab: + $source = GitlabService::class; + break; + + default: + return $results; + } + + // Check if the specified version is a release + try + { + Console::outVerbose(sprintf('Attempting to fetch source code from %s', $definedRemoteSource->Host)); + $release_results = $source::getRelease($packageInput, $definedRemoteSource, $entry); + } + catch(Exception $e) + { + $release_results = null; + unset($e); + } + + // If the specified version is a release, download the source code + if($release_results !== null) + { + $results->ReleaseName = ($release_results->ReleaseName ?? null); + $results->ReleaseDescription = ($release_results->ReleaseDescription ?? null); + $results->Files = self::mergeFilesResults($release_results->Files, ($results->Files ?? null)); + if($release_results->Version !== null) + $results->Version = $release_results->Version; + } + + try + { + $git_results = $source::getGitRepository($packageInput, $definedRemoteSource, $entry); + } + catch(Exception $e) + { + $git_results = null; + unset($e); + } + + if($git_results !== null) + { + if($results->ReleaseName == null) + { + $results->ReleaseName = ($git_results->ReleaseName ?? null); + } + elseif($git_results->ReleaseName !== null) + { + if(strlen($git_results->ReleaseName) > strlen($results->ReleaseName)) + $results->ReleaseName = $git_results->ReleaseName; + } + + if($results->ReleaseDescription == null) + { + $results->ReleaseDescription = ($git_results->ReleaseDescription ?? null); + } + elseif($git_results->ReleaseDescription !== null) + { + if(strlen($git_results->ReleaseDescription) > strlen($results->ReleaseDescription)) + $results->ReleaseDescription = $git_results->ReleaseDescription; + } + + if($results->Version == null) + { + $results->Version = ($git_results->Version ?? null); + } + elseif($git_results->Version !== null) + { + // Version compare + if(VersionComparator::compareVersion($git_results->Version, $results->Version) > 0) + $results->Version = $git_results->Version; + } + + $results->Files = self::mergeFilesResults($git_results->Files, ($results->Files ?? null)); + } + + try + { + $ncc_package_results = $source::getNccPackage($packageInput, $definedRemoteSource, $entry); + } + catch(Exception $e) + { + unset($e); + $ncc_package_results = null; + } + + if($ncc_package_results !== null) + { + if($results->ReleaseName == null) + { + $results->ReleaseName = ($ncc_package_results->ReleaseName ?? null); + } + elseif($ncc_package_results->ReleaseName !== null) + { + if(strlen($ncc_package_results->ReleaseName) > strlen($results->ReleaseName)) + $results->ReleaseName = $ncc_package_results->ReleaseName; + } + + if($results->ReleaseDescription == null) + { + $results->ReleaseDescription = ($ncc_package_results->ReleaseDescription ?? null); + } + elseif($ncc_package_results->ReleaseDescription !== null) + { + if(strlen($ncc_package_results->ReleaseDescription) > strlen($results->ReleaseDescription)) + $results->ReleaseDescription = $ncc_package_results->ReleaseDescription; + } + + if($results->Version == null) + { + $results->Version = ($ncc_package_results->Version ?? null); + } + elseif($ncc_package_results->Version !== null) + { + // Version compare + if(VersionComparator::compareVersion($ncc_package_results->Version, $results->Version) > 0) + $results->Version = $ncc_package_results->Version; + } + + $results->Files = self::mergeFilesResults($ncc_package_results->Files, ($results->Files ?? null)); + } + + return $results; + } + + /** + * Merges the given Files object with another Files object + * + * @param Files $input + * @param Files|null $selected + * @return Files + */ + private static function mergeFilesResults(RepositoryQueryResults\Files $input, ?RepositoryQueryResults\Files $selected=null): RepositoryQueryResults\Files + { + if($selected == null) + $selected = new RepositoryQueryResults\Files(); + + if($input->GitSshUrl !== null) + $selected->GitSshUrl = $input->GitSshUrl; + + if($input->GitHttpUrl !== null) + $selected->GitHttpUrl = $input->GitHttpUrl; + + if($input->SourceUrl !== null) + $selected->SourceUrl = $input->SourceUrl; + + if($input->TarballUrl !== null) + $selected->TarballUrl = $input->TarballUrl; + + if($input->ZipballUrl !== null) + $selected->ZipballUrl = $input->ZipballUrl; + + if($input->PackageUrl !== null) + $selected->PackageUrl = $input->PackageUrl; + + return $selected; + } + + /** + * Attempts to cast the correct type of the given value + * + * @param string $input + * @return float|int|mixed|string + */ + public static function stringTypeCast(string $input): mixed + { + if (is_numeric($input)) + { + if (str_contains($input, '.')) + return (float)$input; + + if (ctype_digit($input)) + return (int)$input; + } + elseif (in_array(strtolower($input), ['true', 'false'])) + { + return filter_var($input, FILTER_VALIDATE_BOOLEAN); + } + + return (string)$input; + } + + /** + * Finalizes the permissions + * + * @return void + * @throws InvalidScopeException + */ + public static function finalizePermissions(): void + { + if(Resolver::resolveScope() !== Scopes::System) + return; + + Console::outVerbose('Finalizing permissions...'); + $filesystem = new Filesystem(); + + try + { + if($filesystem->exists(PathFinder::getDataPath(Scopes::System))) + $filesystem->chmod(PathFinder::getDataPath(Scopes::System), 0777, 0000, true); + } + catch(Exception $e) + { + Console::outWarning(sprintf('Failed to finalize permissions for %s: %s', PathFinder::getDataPath() . DIRECTORY_SEPARATOR . 'data', $e->getMessage())); + } + + try + { + if($filesystem->exists(PathFinder::getCachePath(Scopes::System))) + $filesystem->chmod(PathFinder::getCachePath(Scopes::System), 0777, 0000, true); + } + catch(Exception $e) + { + Console::outWarning(sprintf('Failed to finalize permissions for %s: %s', PathFinder::getDataPath() . DIRECTORY_SEPARATOR . 'data', $e->getMessage())); + } + + } } \ No newline at end of file diff --git a/src/ncc/Utilities/IO.php b/src/ncc/Utilities/IO.php index 7b9c5be..13d76ea 100644 --- a/src/ncc/Utilities/IO.php +++ b/src/ncc/Utilities/IO.php @@ -1,6 +1,26 @@ find($runner); + + if($exec_path !== null) + return $exec_path; + + throw new RunnerExecutionException(sprintf('Unable to find \'%s\' executable', $runner)); + } } \ No newline at end of file diff --git a/src/ncc/Utilities/Resolver.php b/src/ncc/Utilities/Resolver.php index 4d172a0..c58edab 100644 --- a/src/ncc/Utilities/Resolver.php +++ b/src/ncc/Utilities/Resolver.php @@ -1,11 +1,36 @@ $match) @@ -241,4 +266,64 @@ return false; } } + + /** + * Detects the remote source type, can also accept defined remote + * sources as the input, the function will look for the source + * type and return it + * + * @param string $input + * @return string + */ + public static function detectRemoteSourceType(string $input): string + { + if(in_array($input, BuiltinRemoteSourceType::All)) + return RemoteSourceType::Builtin; + + $source_manager = new RemoteSourcesManager(); + $defined_source = $source_manager->getRemoteSource($input); + if($defined_source == null) + return RemoteSourceType::Unknown; + + return RemoteSourceType::Defined; + } + + /** + * Detects the project type from the specified path + * + * @param string $path + * @return ProjectDetectionResults + */ + public static function detectProjectType(string $path): ProjectDetectionResults + { + $project_files = [ + 'project.json', + 'composer.json' + ]; + + $project_file = Functions::searchDirectory($path, $project_files); + + $project_detection_results = new ProjectDetectionResults(); + $project_detection_results->ProjectType = ProjectType::Unknown; + + if($project_file == null) + { + return $project_detection_results; + } + + // Get filename of the project file + switch(basename($project_file)) + { + case 'project.json': + $project_detection_results->ProjectType = ProjectType::Ncc; + break; + + case 'composer.json': + $project_detection_results->ProjectType = ProjectType::Composer; + break; + } + + $project_detection_results->ProjectPath = dirname($project_file); + return $project_detection_results; + } } \ No newline at end of file diff --git a/src/ncc/Utilities/RuntimeCache.php b/src/ncc/Utilities/RuntimeCache.php index 9aad37e..f06ada3 100644 --- a/src/ncc/Utilities/RuntimeCache.php +++ b/src/ncc/Utilities/RuntimeCache.php @@ -1,7 +1,30 @@ remove($file); + Console::outDebug(sprintf('deleted temporary file \'%s\'', $file)); + } + catch (Exception $e) + { + Console::outDebug(sprintf('failed to delete temporary file \'%s\', %s', $file, $e->getMessage())); + unset($e); + } } } } diff --git a/src/ncc/Utilities/Security.php b/src/ncc/Utilities/Security.php index 5e4c4cc..f2d3de2 100644 --- a/src/ncc/Utilities/Security.php +++ b/src/ncc/Utilities/Security.php @@ -1,6 +1,26 @@ ; +chomp($name); +print("Hello, $name\n"); \ No newline at end of file diff --git a/tests/example_project/scripts/unit.py2 b/tests/example_project/scripts/unit.py2 new file mode 100644 index 0000000..af4b858 --- /dev/null +++ b/tests/example_project/scripts/unit.py2 @@ -0,0 +1,3 @@ +print('Hello World!') +name = input('What is your name? ') +print('Hello', name) \ No newline at end of file diff --git a/tests/example_project/scripts/unit.py3 b/tests/example_project/scripts/unit.py3 new file mode 100644 index 0000000..af4b858 --- /dev/null +++ b/tests/example_project/scripts/unit.py3 @@ -0,0 +1,3 @@ +print('Hello World!') +name = input('What is your name? ') +print('Hello', name) \ No newline at end of file