19
$\begingroup$

I am building an application which I would like to deploy as a CDF file, and I am struggling to make the CDF exports work correctly with functions pulled from a .m package file. What are good/best practices for handling such situations?

In particular, the code below encapsulates (at least partly) some of my difficulties. Make a package in the file TestPackage.m with the definitions

BeginPackage["TestPackage`"]
f::usage="f[x] returns a plot.";
Begin["`Private`"]
f[x_]:=Plot[Sin[x y],{y,0,2 Pi},PlotRange->{-1,1},ImageSize->500]
End[]
EndPackage[]

and include it in the same directory as the following code in a notebook.

Needs["TestPackage`", NotebookDirectory[] <> "TestPackage.m"]
DynamicModule[
 {a = 1},
 Column[{
   Slider[Dynamic[a]],
   Dynamic[f[a]]
   }]
 , Initialization :> (
   Needs["TestPackage`", NotebookDirectory[] <> "TestPackage.m"]
   )
 ]
Export[NotebookDirectory[] <> "output.cdf", EvaluationNotebook[]]

The DynamicModule correctly produces the desired output. If I close the notebook and reopen it, the plot renders correctly (which is not the case if the Initialization Needs is omitted). If I open the resulting output.cdf using Mathematica, the plot also renders correctly.

However, if I open output.cdf using the Wolfram CDF Player 10.1, the only thing that shows underneath the Slider is TestPackage`f[1.] (or whatever the value of a is).

I also tried naively spewing the contents of the package onto the initialization rule, along the lines of

Initialization :> (
 ImportString[Import[NotebookDirectory[] <> "TestPackage.m", "Text"],
   "NB"]
 )

but it produces exactly the same behaviour. Presumably there exists a way to do this, but this simply puts the above code into the CDF, which the player then refuses to execute.

How can I make this plot render correctly in the exported CDF version?

$\endgroup$
7
  • 1
    $\begingroup$ I'd have a look at SaveDefinitions option (just a guess. By far not an expert on this). $\endgroup$ Commented Jun 23, 2015 at 21:23
  • 2
    $\begingroup$ The problem is that FreePlayer has I/O restrictions. You can inject with e.g. With your package in text form to Initialization and Get+StringToStream if you don't trust SaveDefinitions. You should be able to incorporate password protected encoded packages with this method. Not perfect but nice enough for free player. $\endgroup$
    – Kuba
    Commented Jun 23, 2015 at 21:29
  • 1
    $\begingroup$ closely related: How to create protected single-file stand-alone CDF applications $\endgroup$
    – Kuba
    Commented Jun 23, 2015 at 21:30
  • $\begingroup$ @Leonid You're right, SaveDefinitions does in fact fix this example. Which means that this doesn't capture whatever is wrong with my bigger code. Back to the drawing board on that, then. Still, if there's a way to replicate the package's definitions in the Initialization stage it should help there. $\endgroup$ Commented Jun 23, 2015 at 21:42
  • 4
    $\begingroup$ @episanty don't worry it's not the point. :) I like this question a lot because it should be stressed out by tutorials of Mathematica how to work with their products. Or rather how to connect different functionalities like Packages Manipulate, Free Player, PlayerPro in context of code security, deployment of standalone files etc etc. So thank's for asking. $\endgroup$
    – Kuba
    Commented Jun 27, 2015 at 13:03

1 Answer 1

21
$\begingroup$

There are some doubts about using SaveDefinitions (1), (2), so let's implement a brute force approach which may be later used automatically.


Outline

Needs @ yourPackage  (*a*)


CDFDeploy @ With[
  { source = Import[packagePath] (*b*) }
, DynamicModule[
    { ... }
  , ...
  , Initialization :> ( Get @ StringToStream @ source )
]
  • (a). You have to call your package before creating DynamicModule because you need all static content (not held/dynamic) in the body of DM to be parsed and evaluated properly.

And all dynamic/held names to be "typeset" with proper context name. And they will be found thanks to $ContextPath modified by BeginPackage and friends. (3)

  • (b). We have to use With to inject source into DynamicModule's Initialization because Initialization is set with RuleDelayed which is HoldRest.

  • For more complex/time consuming packages you may want to change SynchronousInitialization->False and monitor the state of initialization analogously to:

Working with DynamicModule: Tracking the progress of Initialization


###Execution

First code block is only to create a test package:

content = "
  BeginPackage[\"TestPackage`\"]
  f::usage=\"f[x] returns a plot.\";
  Begin[\"`Private`\"]
  f[x_]:=Plot[Sin[x y],{y,0,2 Pi},PlotRange->{-1,1},ImageSize->500]
  End[]
  EndPackage[]";

path = FileNameJoin[{$TemporaryDirectory, "package.m"}];
pathEnc = FileNameJoin[{$TemporaryDirectory, "package.enc"}];
pathCDF = FileNameJoin[{$TemporaryDirectory, "CDF.cdf"}]

Export[path, content, "Text"];

Now the actual code:

Encode[path, pathEnc];  (*c*)
encodedpackage = Import[pathEnc, "Text"];


Get @ pathEnc (*a*)

CDFDeploy[ pathCDF, #, Method -> "Standalone", "Target" -> "CDFPlayer"
] & @ 
With[ { source = encodedpackage (*b*) }
, DynamicModule[{x = 1}
  , Column[{
      Dynamic[{x, f[x]}],
      Button["x++", x++],
      Dynamic @ $ContextPath
    }]
  , Initialization :> Module[{stream}
    , stream = StringToStream @ source
    ; Get @ stream
    ; Close @ stream
    ]
  ]
]

c. we are Encoding source, it is then obscure for others and already compressed so why not. You can even incorporate password protection with this method: (4). It won't be fully secure, as pointed in comments here: (5).

You can now go to the CDF and test in the FreePlayer:

SystemOpen @ DirectoryName @ pathCDF

enter image description here


###Links

(1) The dangers of SaveDefinitions

(2) Not FullDefinition for Save. MachineID not Protected?

(3) load a Mathematica package inside a DynamicModule

(4) Encode->Import->StringToStream->Get on password-locked stream

(5) How to create protected single-file stand-alone CDF applications

$\endgroup$
6
  • $\begingroup$ +1 (a while ago). This is a solid solution that works nicely for probably the vast majority of deployments. It doesn't however, cover the case where one wants to employ "lazy loading" as implemented in a package framework. For example, there may be a corner interface interaction that requires a function whose definition that you don't necessarily want to load on initialization (into the runtime) but only when the user reaches that corner. The way this would be normally be implemented (with DeclarePackage working with file/folder layout) doesn't seem reproducible within a CDF. $\endgroup$ Commented Jan 21, 2019 at 23:06
  • $\begingroup$ @RonaldMonson Thanks. I agree about generality, there are more problems, what if you have two cdfs with slightly different versions of the same package running next to each other. Have not tested it but I assume there will be a collision problem. Is lazy loading so important though? Unless it is a really long package it should not matter these days, should it? $\endgroup$
    – Kuba
    Commented Jan 21, 2019 at 23:16
  • $\begingroup$ Yes, good point about two cdfs. It depends on what is more important. Total encapsulation within each interface seems intuitively preferable thereby eliminating any collision issue (I think Paul Abbott has argued that Manipulate, DynamicModule should come with own contexts). On the other hand, this becomes inefficient when you have lots of interfaces that might want to share the same package. As to lazy loading being (in)sufficiently important, as I suggested, probably not for the vast majority of deployments currently but I think it could be and without it you put a ceiling on the richness .. $\endgroup$ Commented Jan 21, 2019 at 23:45
  • $\begingroup$ ...of possible interfaces. Also, If you do have a scenario where your cdfplayer is playing lots of interfaces then the advantages compound and you kind of also want to get all the benefits of your carefully created modularizations. At any rate, there are already some barriers to richer interfaces so perhaps this is a moot point anyway and with cdf's going away it seems premature to solve this now. Still, given movement on the package development front, developing an ecosystem of packages in concert with developing a better ecosystem of interacting interfaces seems worth considering. $\endgroup$ Commented Jan 21, 2019 at 23:53
  • $\begingroup$ @RonaldMonson FYI I am working on automating it github.com/kubaPod/NotebookApps, I'd say it is a solid alpha release :) $\endgroup$
    – Kuba
    Commented Mar 2, 2019 at 0:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.