Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Middleware for customization of all recipes #9636

Closed
gmeeker opened this issue Sep 20, 2021 · 1 comment
Closed

[feature] Middleware for customization of all recipes #9636

gmeeker opened this issue Sep 20, 2021 · 1 comment

Comments

@gmeeker
Copy link

gmeeker commented Sep 20, 2021

Allow users to inject code to modify all recipes, allowing users to run additional commands in all build() and package() methods. One use case would be code signing all executables. The ultimate goal would be support for multiple architectures; see below. This feature request should cover only the core functionality of middleware support.

Because of the complexity of this idea, I've been experimenting with coding prototypes rather than abstract designs, to see how they play out in practice. I think the implementation in #9637 is finally ready for feedback. This conceptually easier to understand than previous implementations (which created wrappers around classes. It's more isolated and doesn't touch the ConanFile model or the graph code. It handles the altered package ids and host/build profiles without extra work.

It should be noted that some of this can implemented with hooks, but the result is pretty awful, and highly dependent on conan's internals. One such attempt:
https://github.com/gmeeker/conan-multi-build-hook

Extending conanfiles

A middleware is an adapter that can subclass any or all conanfiles, and can be chained with other middleware.

A complete example is here:
https://github.com/gmeeker/conan-codesign-middleware

class codesign(Middleware):
    settings = "os", "codesign_identity", "codesign_flags"
    options = {"identity": [None, "ANY"], "flags": [None, "ANY"]}
    default_options = {"identity": None, "flags": None}

    @property
    def identity(self):
        return self.settings.codesign_identity or self.options.identity

    @property
    def flags(self):
        return self.settings.codesign_flags or self.options.flags

    def should_apply(self, base):
        if not self.identity:
            return False
        return True

    def __call__(self, base):
        class CodeSignConan(base):
            # Some settings might only be used in 'should_apply' but we use all here
            settings = Middleware.extend(base.settings, codesign.settings)
            options = Middleware.extend(base.options, codesign.options)
            default_options = Middleware.extend(base.default_options, codesign.default_options)

            @property
            def identity(self):
                return self.settings.codesign_identity or self.options.identity

            @property
            def flags(self):
                return self.settings.codesign_flags or self.options.flags

            def package(self):
                super(CodeSignConan, self).package()
                self.run(...)

        return CodeSignConan

Loading middleware

Because middleware alters conanfile classes, it must be handled before any other loading. I propose a new profile field
[middleware] which references middleware by name (like generators). [middleware_requires] is necessary to load packages before [build_requires] processing.

[settings]
codesign_identity=My Company
[middleware_requires]
codesign/0.1@
[middleware]
codesign
[env]

Alternatively, middleware could be loaded from the local cache, much like generators. Copy to ~/.conan/middleware/codesign.py and remove the ConanFile part. You still need to enable the middleware in the profile.

[settings]
codesign_identity=My Company
[middleware]
codesign
[env]

It's also possible to constraint this to specific packages:

[middleware_requires]
 my_pkg*: codesign/0.1@
 &: codesign/0.1@
 &!: codesign/0.1@
[middleware]
 my_pkg*: codesign
 &: codesign
 &!: codesign

Settings

As shown in the example above, settings and options work like any conanfile. They can be applied to a specific middleware by name too. When a middleware creates a new subclass, it must mix in any settings or options that will be required.

Multiple architectures
While multi-arch is beyond the scope of this initial feature request, I think it's important to show why this is desirable, and possibly to influence the discussion of settings and configuration. This idea was born out of the desire to support multi-arch and previous discussion around an all-in-one feature seems to have stalled.

conan-io/conan-extensions#59
#6384

By keeping all tool specific code outside Conan, this middleware proposal allows user customization without the maintenance or compatibility concerns of trying to implement multi-arch directly. I believe it can encourage development of experimental features without requiring familiarity with Conan's source code.

An earlier middleware and multi-arch attempt is here in the middleware branch, which is now substantially different. I will be adapting this to use this new middleware approach.
[https://github.com/gmeeker/conan]

macOS/iOS multi-arch middleware is here, using pyreq to provide settings:
[https://github.com/gmeeker/conan-lipo-middleware]

(I'm not familiar with Android, but it looks like there are similar issues there as well.)

Previous implementation in #9637 was very different. Middleware behaved like a conanfile and the result looked like this. This lead to confusion between conanfile settings and middleware settings.

class codesign(Middleware):
    settings_middleware = "os", "codesign_identity"

    def valid(self):
        # Is this middleware relevant for this conanfile?
        return True

    def package(self):
        self.conanfile.package()
        self.run(...)

class CodesignPackage(ConanFile):
    name = "codesign"
    version = "0.1"
@memsharded
Copy link
Member

Closing because of #9859 (comment)

@memsharded memsharded closed this as not planned Won't fix, can't repro, duplicate, stale May 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants